深入学习 Prompt(提示词) 组件是掌握 LangChain 的基石。在 LangChain 中,Prompt 不仅仅是一个字符串,它是一个管理输入逻辑的工厂 。

LangChain 的 Prompt 组件主要解决两个问题:
- 动态替换:把用户输入(如"苹果")填入固定的模板(如"请介绍一下{fruit}")。
- 角色管理:在对话模型中,区分"系统指令"、"用户提问"和"历史记录"。
下面我将分层次、全方位地讲解,并附带代码示例。不是没讲invoke()是在后面部分讲解的一定要看到最后.
第一部分:核心概念 ------ 两种主要的 Prompt 模板
LangChain 主要提供两种模板,分别对应文本补全模型(老式)和聊天模型(现代,如 GPT-3.5/4, DeepSeek)。
1. PromptTemplate (字符串模板)
这是最基础的,用于生成一个单纯的字符串。它不区分角色,就是把变量填进去。
- 适用场景:简单的文本生成,或非对话模型。
- 语法 :使用 Python 的 f-string 风格
{variable}。
代码实战:
python
from langchain_core.prompts import PromptTemplate
# 1. 定义模板
template = PromptTemplate.from_template(
"你是一个{profession},请用{style}的语气介绍一下你自己。"
)
# 2. 填充变量 (看看它变成了什么)
formatted_prompt = template.format(profession="相声演员", style="幽默")
print("=== 生成的 Prompt 字符串 ===")
print(formatted_prompt)
# 输出: 你是一个相声演员,请用幽默的语气介绍一下你自己。
2. ChatPromptTemplate (聊天提示词模板) ------ 重点掌握
这是目前最常用的。它生成的不是一个字符串,而是一个 Message 列表 。它完美对应了 OpenAI/DeepSeek 接口中的 messages 参数。
它包含三种核心角色(Message Types):
- System (系统):设定的"人设"或"规则"(例如:你是一个翻译官)。
- Human (人类):用户的输入。
- AI (模型):模型回复的内容(通常用于由你伪造历史记录,或是 Few-shot 示例)。
代码实战:
python
from langchain_core.prompts import ChatPromptTemplate
# 方式一:使用元组 (推荐,最简洁)
chat_template = ChatPromptTemplate.from_messages([
("system", "你是一个专业的{language}翻译官。"),
("human", "请翻译这句话:{text}"),
])
# 方式二:使用对象类 (更严谨,适合复杂逻辑)
# from langchain_core.messages import SystemMessage, HumanMessage
# chat_template = ChatPromptTemplate.from_messages([
# SystemMessage(content="你是一个专业的{language}翻译官。"),
# HumanMessage(content="请翻译这句话:{text}"),
# ])
# 填充变量
messages = chat_template.format_messages(language="文言文", text="编程真好玩")
print("=== 生成的 Message 列表 ===")
for msg in messages:
print(f"[{msg.type}]: {msg.content}")
第二部分:进阶用法 ------ 让 Prompt 更强大
掌握了基础后,实际开发中你很快会遇到以下需求:
1. MessagesPlaceholder (用于管理历史对话)
做聊天机器人时,你无法预知用户会聊多少句。你需要一个"占位符",把之前的聊天记录(History)一股脑塞进去。
场景:带记忆的聊天机器人。
代码实战:
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
chat_template = ChatPromptTemplate.from_messages([
("system", "你是一个暴躁的客服。"),
# 这是一个占位符,专门用来放历史记录列表
MessagesPlaceholder(variable_name="history"),
("human", "{user_input}")
])
# 模拟之前的对话历史
fake_history = [
HumanMessage(content="我想退款"),
AIMessage(content="退款?门都没有!"),
HumanMessage(content="我要投诉你")
]
# 填充
messages = chat_template.format_messages(
history=fake_history, # 把列表塞进去,注意这个history必须是你在MessagePlaceholder中声明的变量.
user_input="你这什么态度?"
)
print("=== 包含历史记录的 Prompt ===")
print(messages)
2. Few-Shot Prompting (少样本提示) ------ 提分秘籍
大模型有时候听不懂指令,但看懂例子。"与其告诉它怎么做,不如演示给它看"。
LangChain 提供了 FewShotChatMessagePromptTemplate 来标准化这个过程。
场景:让模型按特定格式输出,或学习特定的风格(如:反义词生成器)。
用**"教徒弟"**的比喻,把这个概念拆碎了讲.
- 什么是 Few-Shot?(核心逻辑)
假设你要教一个新来的徒弟(大模型)做"把现代汉语翻译成古文"。
-
Zero-Shot(零样本) :
你直接命令他:"把'你好'翻译成古文。"
- 徒弟可能会懵:是翻译成《诗经》风?还是《水浒》风?
-
Few-Shot(少样本/举例子) :
你先给他看几个已经做好的标准答案,然后再让他做题。
- 你:"看着啊,'早上好'翻译成'早安';'吃了吗'翻译成'用膳否'。懂了吗?好,现在把'再见'翻译一下。"
- 徒弟瞬间秒懂:哦!原来你要这种风格的!那'再见'就是'后会有期'。
总结: Few-Shot 就是在提问之前,先伪造几段"完美的对话历史",让大模型照猫画虎,模仿你的格式和风格。
- 在 LangChain 中怎么实现?(三步走)
在 LangChain 中,实现 Few-Shot 就像**"做汉堡"**,我们需要分层组装。
第一步:准备"馅料" (定义例子列表)
首先,你要准备好几个标准的"输入-输出"对。这就是你要喂给模型的样本。
python
# 1. 准备样本数据 (List of Dictionaries)
examples = [
{
"input": "天气真好",
"output": "天朗气清,惠风和畅"
},
{
"input": "我好饿啊",
"output": "饥肠辘辘,腹空如洗"
},
{
"input": "这东西太贵了",
"output": "物贵虽奇,囊中羞涩"
}
]
第二步:准备"模具" (定义单个例子的格式)
你要告诉 LangChain,每一个例子在对话中长什么样。
通常,一个例子就是:用户说一句 (HumanMessage) -> AI 回一句 (AIMessage)。
python
from langchain_core.prompts import ChatPromptTemplate
# 2. 定义单个例子的模版
# 这意味着:对于上面列表里的每一对数据,我都把它变成一轮对话
example_prompt = ChatPromptTemplate.from_messages([
("human", "{input}"),
("ai", "{output}")
])
第三步:组装"汉堡" (生成 FewShot 模板)
现在使用 LangChain 专门的组件 FewShotChatMessagePromptTemplate 把上面两步结合起来。
python
from langchain_core.prompts import FewShotChatMessagePromptTemplate
# 3. 创建 Few-Shot 模版
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt, # 使用刚才定义的模具
examples=examples, # 填入刚才准备的馅料
)
- 完整代码实战与效果演示
我们把上面三步串联起来,看看最终发给大模型的到底是什么。
python
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
# --- 1. 准备数据 ---
examples = [
{"input": "天气真好", "output": "天朗气清,惠风和畅"},
{"input": "我好饿啊", "output": "饥肠辘辘,腹空如洗"},
]
# --- 2. 定义格式 ---
example_prompt = ChatPromptTemplate.from_messages([
("human", "{input}"),
("ai", "{output}"),
])
# --- 3. 组装 FewShot 组件 ---
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=examples,
)
# --- 4. 组装最终的大 Prompt ---
# 结构通常是:[系统指令] -> [那是几个示例] -> [用户真正的问题]
final_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个文采飞扬的古人,请把现代白话文转化为文言文。"),
few_shot_prompt, # <--- 这里把示例插进去了!
("human", "{user_input}")
])
# --- 5. 见证奇迹 (Invoke) ---
# 假设用户输入了 "我很开心"
messages = final_prompt.invoke({"user_input": "我很开心"})
# 打印出来看看,LangChain 到底背着我们生成了什么?
print("=== 发给模型的实际消息列表 ===")
for msg in messages.to_messages():
print(f"[{msg.type}]: {msg.content}")
运行结果(非常重要,请仔细看):
你会发现,虽然你只输入了一句话,但 LangChain 帮你生成了 5 条消息发给模型:
text
=== 发给模型的实际消息列表 ===
[system]: 你是一个文采飞扬的古人,请把现代白话文转化为文言文。
[human]: 天气真好 <-- 示例 1 开始
[ai]: 天朗气清,惠风和畅 <-- 示例 1 结束
[human]: 我好饿啊 <-- 示例 2 开始
[ai]: 饥肠辘辘,腹空如洗 <-- 示例 2 结束
[human]: 我很开心 <-- 这才是用户刚才真正输入的!
原理解析:
大模型看到这个列表时,它会以为前面两轮对话是真的发生过的。它会想:"哦,原来这个用户的习惯是这样说话的,那我也要保持队形。"
于是,针对最后一句"我很开心",它更有可能回答:"心旷神怡,喜不自胜",而不是简单的"我很愉快"。
- 为什么要这么麻烦?直接写在 System 里不行吗?
你可能会问:我直接在 System 里写"举个例子:天气好->天朗气清",不行吗?
可以,但是 Few-Shot 组件有两个巨大的优势:
-
防止 Token 溢出(动态选择) :
假设你有 100 个例子(总共有 5万字),你不能全塞进 Prompt 里,因为会把额度用光。
LangChain 的
FewShot组件支持**"示例选择器 (ExampleSelector)"**。它可以根据用户输入的问题,只挑选最相关的 3 个例子塞进去!- 用户问数学题 -> 挑数学例子
- 用户问历史题 -> 挑历史例子
- (这是进阶功能,目前先了解即可)
-
格式更标准 :
相比于把例子像写作文一样堆在 System 里,使用
Human/AI的消息对格式,能让模型更清晰地理解**"哪句是输入,哪句是输出"**。
- Few-Shot 就是**"照猫画虎"**。
- 它通过伪造历史对话记录来教模型做事。
- 在 LangChain 中,你只需要准备好列表 (List) 和 格式 (Prompt) ,剩下的组装工作由
FewShotChatMessagePromptTemplate自动完成。
第四部分:综合实战 ------ 结合 API 调用,后面会详细介绍最核心的'链(Chain)'
光生成 Prompt 没用,最后我们要把它传给 API(LLM)。我们用 LCEL (LangChain Expression Language) 也就是 | 来串联。
假设你已经配置好了 llm
完整流程代码:
python
import os
# 假设你已经定义了 llm
from langchain_openai import ChatOpenAI
# 替换为你的 key 和 base_url
llm = ChatOpenAI(
model="deepseek-ai/DeepSeek-V3",
openai_api_key="sk-xxxx",
openai_api_base="https://api.siliconflow.cn/v1"
)
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser # 输出解析器后面讲解
# 1. 定义 Prompt (带人设)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个资深的Python代码评审专家。请指出代码中的错误并优化它。"),
("human", "{code_snippet}")
])
# 2. 定义解析器 (直接拿字符串,不要 Message 对象)
parser = StrOutputParser()
# 3. 组成链:Prompt -> LLM -> Parser
chain = prompt | llm | parser
# 4. 准备一段烂代码
bad_code = """
def add(a, b):
print(a+b)
"""
# 5. 运行
print("Waiting for response...")
response = chain.invoke({"code_snippet": bad_code}) # invoke一定要看到后面!!!
print("\n=== AI 回复 ===")
print(response)
前面的讲解中,为了看清楚 Prompt 到底生成了什么"原始数据"(即 List of Messages),特意使用了 format_messages。但在实际的 LangChain LCEL (LangChain Expression Language) 开发中,99% 的情况我们都会使用 invoke。
下面详细讲解 invoke 在 Prompt 组件中的用法,并做一个深度的对比。
1. 为什么现在都用 invoke?
LangChain 引入了一套新标准,叫 Runnable Protocol(可运行协议) 。
在这个协议下,所有的组件 (Prompt、LLM、OutputParser、Chain)都实现了同一个方法:invoke 。invoke的汉语意思是调用,非常见名之意🥳🥳🥳
这样做的好处是:接口统一。
- 你不需要记
prompt.format() - 也不需要记
llm.predict() - 更不需要记
parser.parse() - 统统都用
invoke()!
2. Prompt 组件使用 invoke 的实战
让我们把之前的例子改写成 invoke 版本。
代码示例:
python
from langchain_core.prompts import ChatPromptTemplate
# 1. 定义模版
template = ChatPromptTemplate.from_messages([
("system", "你是一个专业的{role}。"),
("human", "请解释一下:{concept}")
])
# ==========================================
# 方式 A: 旧方法 (便于调试,看原始数据)
# ==========================================
messages = template.format_messages(role="数学老师", concept="微积分")
print("=== A. format_messages 的结果 (List) ===")
print(type(messages))
# 输出: <class 'list'>
print(messages)
# 输出: [SystemMessage(...), HumanMessage(...)]
# ==========================================
# 方式 B: 新方法 (标准用法,用于 LCEL)
# ==========================================
prompt_value = template.invoke({"role": "数学老师", "concept": "微积分"})
print("\n=== B. invoke 的结果 (PromptValue) ===")
print(type(prompt_value))
# 输出: <class 'langchain_core.prompt_values.ChatPromptValue'> (注意这里!)
print(prompt_value)
# 输出: messages=[SystemMessage(...), HumanMessage(...)]
关键区别点(敲黑板):
format_messages返回的是一个 Python List(列表)。invoke返回的是一个 ChatPromptValue 对象。
ChatPromptValue 是一个包装器,它既包含消息列表,也包含生成后的字符串。它是连接 Prompt 和 LLM 的通用桥梁。
3. 深度对比:format vs invoke
| 特性 | format / format_messages |
invoke (推荐) |
|---|---|---|
| 所属体系 | 旧版 / 字符串处理逻辑 | LCEL (LangChain 表达式语言) |
| 输入参数 | 关键字参数 (a=1, b=2) |
字典 {"a": 1, "b": 2} |
| 返回值 | String 或 List[Message] |
PromptValue 对象 (更高级的包装) |
| 主要用途 | 调试 (Print出来看看对不对) | 生产 (在链中传递数据) |
| 兼容性 | 无法直接被 ` | ` 管道符连接 |
4. 为什么 invoke 在链(Chain)中如此重要?
如果你不使用 invoke,你就无法使用 LangChain 最强大的 管道操作符 (|)。
请看下面这个对比:
如果没有 invoke (传统的、痛苦的写法):
python
# 你需要手动一步步传数据
formatted_input = prompt.format_messages(role="老师", concept="数学")
response = llm.predict_messages(formatted_input)
result = parser.parse(response.content)
使用 invoke (LCEL 的优雅写法):
LangChain 会自动在内部调用 invoke。
python
# 1. 定义链
chain = prompt | llm | parser
# 2. 一键运行
# 这里你调用的 chain.invoke,其实内部自动调用了 prompt.invoke
result = chain.invoke({"role": "老师", "concept": "数学"})
先简单讲解一下链:
当你执行 chain.invoke({...}) 时,数据流是这样的:
- 字典
{"role":...}传给 Prompt。 - Prompt 自动调用
invoke,生成ChatPromptValue。 ChatPromptValue传给 LLM。- LLM 自动调用
invoke,生成AIMessage。 AIMessage传给 Parser。- Parser 自动调用
invoke,生成最终字符串。
总结
- 真正写代码/构建应用时 :请一律使用
invoke。 - 记忆口诀 :
- Prompt 是一个 Runnable(可运行对象)。
- 所有 Runnable 都要用
invoke启动。 invoke接收字典,返回对象。
现在你已经掌握了最核心的机制,接下来在学习 Chain(链)的时候,你会发现 invoke 无处不在!
总结 ------ 你需要记住什么?
- 如果你用 GPT/DeepSeek/通义千问 :请死磕
ChatPromptTemplate - 结构公式 :
System(人设) ->MessagesPlaceholder(历史) ->Human(新问题)。 - 调试技巧 :如果 AI 回答不符合预期,先打印
prompt.invoke(dict1).to_string()看看你到底发给 AI 了什么内容,通常是 Prompt 拼写有问题。
这就是 LangChain Prompt 组件最核心、最实用的部分!你可以试着把上面的 Few-Shot 例子跑一下,看看效果是不是比直接问更好。