LangChain 学习记录 03:提示词模板 PromptTemplate 与 ChatPromptTemplate 详解

LangChain 学习记录 03:提示词模板 PromptTemplate 与 ChatPromptTemplate 详解

摘要

在大模型应用开发里,Prompt 不是随手拼出来的一段字符串,而是影响模型输出质量、代码可维护性和项目稳定性的核心资产。

本文围绕 LangChain 的提示词模板展开,重点讲清楚:

  • 为什么不要长期依赖 f-string 手动拼 Prompt
  • PromptTemplateChatPromptTemplate 分别适合什么场景
  • partialpartial_variables 如何减少重复传参
  • MessagesPlaceholder 为什么是多轮对话的关键
  • FewShotPromptTemplate 如何用示例引导模型输出

一句话概括:

text 复制代码
Prompt Template 的作用,是把"怎么问模型"这件事标准化、参数化、可复用。

文章目录

一、为什么需要提示词模板

刚开始调用大模型时,我们很容易这样写:

python 复制代码
topic = "向量数据库"
style = "通俗易懂"

prompt = f"请用{style}的方式解释什么是{topic}"

这当然能跑。

但真实项目里的 Prompt 往往不是一句话,而是包含:

  • 角色设定
  • 任务目标
  • 输入数据
  • 输出格式
  • 约束条件
  • 示例
  • 历史对话
  • 安全边界

如果全部用字符串拼接,代码会慢慢变成这样:

python 复制代码
prompt = (
    "你是一个专业助手。\n"
    f"请分析下面内容:{text}\n"
    "要求:\n"
    "1. 输出 JSON\n"
    "2. 不要添加解释\n"
    f"3. 语气要{style}\n"
)

一开始看着还行,后面需求一改,Prompt 四处散落,维护起来就像在旧项目里找一个没有注释的全局变量,心态非常容易裂开。

提示词模板解决的就是这个问题:

text 复制代码
把 Prompt 的固定部分和可变部分分离。

也就是:

text 复制代码
固定模板 + 动态变量 = 最终 Prompt

二、Prompt Template 的底层逻辑

先看一张逻辑图。

LangChain 中的提示词模板大概遵循这个流程:

text 复制代码
变量输入
  ↓
PromptTemplate / ChatPromptTemplate
  ↓
PromptValue
  ↓
Chat Model 或 LLM

这里有一个容易忽略的点:

text 复制代码
PromptTemplate 调用后,不一定直接得到普通字符串。

它可能生成一个 PromptValue,这个中间对象可以适配不同模型:

  • 给普通 LLM 时,可以转成字符串
  • 给 Chat Model 时,可以转成消息列表

这就是 LangChain 组件能无缝接入 LCEL 链的原因。

后面你会经常看到:

python 复制代码
chain = prompt | model | parser

这里的 prompt 能接到 model 前面,就是因为它们都遵循统一的输入输出协议。

三、PromptTemplate:最基础的字符串模板

PromptTemplate 适合简单文本生成场景。

比如:

python 复制代码
from langchain_core.prompts import PromptTemplate

prompt = PromptTemplate.from_template(
    "请用{style}的风格解释什么是{topic}。"
)

result = prompt.invoke({
    "style": "通俗易懂",
    "topic": "向量数据库"
})

print(result)

这里的 {style}{topic} 是占位符。

调用时传入:

python 复制代码
{
    "style": "通俗易懂",
    "topic": "向量数据库"
}

LangChain 会把变量填进模板,生成最终 Prompt。

你也可以把它理解成一个函数:

python 复制代码
def prompt(style, topic):
    return f"请用{style}的风格解释什么是{topic}。"

只不过 LangChain 的模板更适合后续接入模型、解析器和 Chain。

常见写法对比

写法 说明 适合场景
f-string Python 原生字符串拼接 临时实验、小脚本
PromptTemplate LangChain 字符串模板 单轮文本生成
ChatPromptTemplate 聊天消息模板 对话模型、多轮问答
FewShotPromptTemplate 示例驱动模板 需要给模型示范输出模式

如果你只是写一个极小 demo,f-string 没问题。

但只要准备做项目,就建议尽早换成 Prompt Template。

四、ChatPromptTemplate:聊天模型的主力模板

现在大多数大模型接口都属于 Chat Model。

Chat Model 不只是接收一段字符串,而是接收一组消息:

text 复制代码
system:系统规则
human:用户输入
ai:模型历史回复

所以更常用的是 ChatPromptTemplate

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一名耐心的技术导师,擅长用简单例子解释复杂概念。"),
    ("human", "请解释:{question}")
])

prompt_value = prompt.invoke({
    "question": "LangChain 中的 PromptTemplate 有什么用?"
})

print(prompt_value)

这段模板里有两条消息:

text 复制代码
system:给模型设定身份和行为规则
human:用户真正要问的问题

system、human、ai 的区别

角色 作用 示例
system 设定模型身份、规则、边界 你是一个严谨的代码审查助手
human 用户当前输入 请解释这段代码
ai 模型之前的回复,常用于示例或历史 这段代码的作用是...

一个更贴近项目的例子:

python 复制代码
review_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个资深 Python 代码审查助手。"),
    ("human", "请审查下面代码,指出潜在问题:\n{code}")
])

这个模板比普通字符串更清晰:

  • 系统身份放在 system
  • 用户任务放在 human
  • 变量 {code} 独立传入

代码更像在描述业务流程,而不是乱拼字符串。

五、partial 与 partial_variables

有时模板里有些变量是固定的。

比如:

python 复制代码
prompt = PromptTemplate.from_template(
    "请用{style}风格解释{topic},要求不超过{max_words}字。"
)

如果 style 总是"通俗易懂",我们不想每次都传:

python 复制代码
prompt = prompt.partial(style="通俗易懂")

之后调用时只需要传剩下的变量:

python 复制代码
result = prompt.invoke({
    "topic": "RAG",
    "max_words": 100
})

partial 的核心价值

text 复制代码
提前填充一部分变量,让模板变成更具体的模板。

这有点像函数默认参数:

python 复制代码
def explain(topic, style="通俗易懂"):
    ...

partial 和 partial_variables 区别

方式 使用时机 特点
partial() 模板创建之后再填充变量 灵活,适合运行时决定
partial_variables 模板创建时就固定变量 更自描述,适合固定配置

示例:

python 复制代码
prompt = PromptTemplate(
    template="请用{style}风格解释{topic}",
    input_variables=["topic"],
    partial_variables={
        "style": "通俗易懂"
    }
)

如果这个固定值来自用户选择、配置文件或运行时上下文,优先用 partial()

如果这个固定值就是模板定义的一部分,可以用 partial_variables

六、MessagesPlaceholder:给历史对话留位置

多轮对话中,我们需要把历史聊天记录插入 Prompt。

这时就要用:

python 复制代码
MessagesPlaceholder

示例:

python 复制代码
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个耐心的学习助手。"),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

这里的:

python 复制代码
MessagesPlaceholder(variable_name="history")

可以理解为:

text 复制代码
这里先空着,运行时把历史消息列表塞进来。

运行时可以传:

python 复制代码
prompt.invoke({
    "history": [
        ("human", "我叫张三"),
        ("ai", "你好,张三")
    ],
    "input": "我叫什么?"
})

最终模型看到的上下文大概是:

text 复制代码
system:你是一个耐心的学习助手
human:我叫张三
ai:你好,张三
human:我叫什么?

重点:

text 复制代码
MessagesPlaceholder 本身不保存历史,它只是给历史消息留一个插槽。

真正自动保存和读取历史,后面通常会配合 RunnableWithMessageHistory

七、FewShotPromptTemplate:用示例教模型做事

有些任务光靠一句指令不够稳定。

比如你希望模型把情绪词转换成近义词:

text 复制代码
高兴 -> 开心
难过 -> 悲伤
生气 -> 愤怒

这时可以用 Few-shot prompting,也就是给模型几个示例。

python 复制代码
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate

examples = [
    {"input": "高兴", "output": "开心"},
    {"input": "难过", "output": "悲伤"},
    {"input": "生气", "output": "愤怒"},
]

example_prompt = PromptTemplate.from_template(
    "输入:{input}\n输出:{output}"
)

prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    prefix="请把用户输入的情绪词转换成一个近义词。",
    suffix="输入:{word}\n输出:",
    input_variables=["word"]
)

result = prompt.invoke({
    "word": "开心"
})

print(result)

生成的 Prompt 大致是:

text 复制代码
请把用户输入的情绪词转换成一个近义词。

输入:高兴
输出:开心

输入:难过
输出:悲伤

输入:生气
输出:愤怒

输入:开心
输出:

Few-shot 的底层逻辑是:

text 复制代码
不是只告诉模型规则,而是给模型看"输入和输出应该长什么样"。

这对以下任务特别有用:

  • 分类
  • 格式转换
  • 风格模仿
  • 固定结构输出
  • 少量业务规则示范

八、项目中怎么选

可以用这张表快速判断。

场景 推荐模板 原因
简单文本生成 PromptTemplate 轻量,适合字符串 Prompt
调用聊天模型 ChatPromptTemplate 支持 system/human/ai 角色
多轮对话 ChatPromptTemplate + MessagesPlaceholder 可以插入历史消息
需要示例引导 FewShotPromptTemplate 用样例稳定模型行为
固定部分变量 partial() 减少重复传参

我的建议:

text 复制代码
如果你现在主要用 ChatOpenAI、ChatOllama 等聊天模型,优先掌握 ChatPromptTemplate。

PromptTemplate 要会,但真实项目里 ChatPromptTemplate 更常用。

九、常见坑

1. 模板变量名和 invoke 输入不一致

模板里写的是:

python 复制代码
"请解释:{topic}"

调用时却传:

python 复制代码
{"question": "LangChain"}

这样会报错。

正确写法:

python 复制代码
{"topic": "LangChain"}

变量名要对上,程序不认"差不多"。

2. 把 ChatPromptTemplate 当成普通字符串

ChatPromptTemplate 生成的是消息结构,不是简单字符串。

它的优势正是能区分:

text 复制代码
system / human / ai

不要强行把它当普通字符串拼来拼去。

3. MessagesPlaceholder 传入的不是消息列表

错误思路:

python 复制代码
{"history": "用户:你好\nAI:你好"}

更推荐:

python 复制代码
{
    "history": [
        ("human", "你好"),
        ("ai", "你好,有什么可以帮你?")
    ]
}

因为聊天模型更容易理解结构化消息。

4. Few-shot 示例太多

示例不是越多越好。

示例太多会:

  • 增加 token 成本
  • 挤占上下文窗口
  • 让 Prompt 变难维护

通常先从 2 到 5 个高质量示例开始。

十、小结

这一篇主要讲了 LangChain 的提示词模板。

你可以这样记:

text 复制代码
PromptTemplate:普通字符串模板。
ChatPromptTemplate:聊天消息模板。
MessagesPlaceholder:给历史消息留插槽。
FewShotPromptTemplate:用示例引导模型。
partial:提前填充部分变量。

最重要的是这句话:

text 复制代码
提示词模板不是为了少写字符串,而是为了让 Prompt 变得可复用、可维护、可组合。

下一篇继续学习:Output Parser 输出解析器

如果说 Prompt Template 负责"怎么问",那么 Output Parser 就负责"怎么把模型回答变成程序能用的数据"。

参考资料

相关推荐
向日的葵0062 小时前
langchain的Tools教程(三)
python·langchain·tools
xian_wwq2 小时前
【学习笔记】模型权重管理:Safetensors与私有化Hub(23/35)
笔记·学习
其实防守也摸鱼3 小时前
运维--学习阶段问题解答(1)(自测)
linux·运维·服务器·数据库·学习·自动化·命令模式
ctrl_v助手3 小时前
Halcon学习笔记2
人工智能·笔记·学习
YUS云生4 小时前
Python学习笔记·第31天:FastAPI入门——路由、路径参数、查询参数与请求体
笔记·python·学习
铅笔侠_小龙虾4 小时前
Rust 学习目录
开发语言·学习·rust
渣渣灰飞6 小时前
MySQL 系统学习 第五阶段:企业级 MySQL 实战开发 第二章:RBAC 权限系统设计
android·学习·mysql
六bring个六6 小时前
open Harmony中分布式软总线的学习任务清单
分布式·学习·c/c++·open harmony
精神底层6 小时前
AI 学习笔记:研究方法的演变
人工智能·笔记·学习
依然范特东7 小时前
RAG学习总结3--检索技术
笔记·学习