Prompt 进阶------系统讲解
为什么要"Prompt 进阶"?
普通调用方式:llm.invoke("你好") 够用,但有三个问题:
- 格式不稳定:LLM 每次输出格式可能不同
- 推理质量差:复杂逻辑题容易出错
- 示例复用难:想让 LLM 模仿某种风格,只能每次手写
Prompt 进阶就是解决这三个问题的工程方法。
Chat 模型吃的是「消息列表」
像 GPT 这类 Chat 模型,输入不是一段纯文本,而是一组有角色的消息:
json
[
{"role": "system", "content": "你是助手"},
{"role": "user", "content": "苹果什么颜色?"},
{"role": "assistant", "content": "通常是红色"},
{"role": "user", "content": "草莓呢?"}, # 当前问题
]
LangChain 里对应的消息类型是:
- SystemMessage
- HumanMessage(用户)
- AIMessage(助手)
ChatPromptTemplate 的作用就是:用模板描述「消息列表应该长什么样」,运行时填入变量,生成真正的消息列表。
ChatPromptTemplate.from_messages
hatPromptTemplate 是「对话式 Prompt 模板」;from_messages 是它的工厂方法,用消息列表来定义模板结构。
参数格式
from_messages 接受一个列表,每个元素可以是:
| 写法 | 含义 |
|---|---|
| ("system", "固定文本") | 系统消息,内容写死 |
| ("human", "{变量名}") | 用户消息,带占位符 |
| ("ai", "{变量名}") | 助手消息(用于 few-shot 示例里的「标准答案」) |
| 子模板对象 | 如 FewShotChatMessagePromptTemplate、MessagesPlaceholder |
它做什么?
- 定义结构:这段对话由哪些消息、什么顺序组成
- 声明变量:{question} 这类占位符,调用时要传入
- 生成消息:invoke({"question": "草莓什么颜色?"}) 时,把变量填进去,返回 ChatPromptValue(本质是消息列表)
简单例子
py
prompt = ChatPromptTemplate.from_messages([
("system", "你是颜色助手"),
("human", "{question}"),
])
# 调用
messages = prompt.invoke({"question": "草莓什么颜色?"})
# 实际生成(概念上):
# [
# SystemMessage("你是颜色助手"),
# HumanMessage("草莓什么颜色?"),
# ]
常见用途
- 系统指令 + 用户问题(最常见)
- 多轮对话(配合 MessagesPlaceholder 插入历史)
- 作为 FewShotChatMessagePromptTemplate 的 example_prompt(定义单条示例格式)
FewShotChatMessagePromptTemplate
专门用于 Few-shot(少样本)提示 的子模板。
它不负责整段 prompt,只负责:把多条示例数据,批量展开成多轮 human/ai 对话消息。
核心参数
py
FewShotChatMessagePromptTemplate(
examples=[...], # 示例数据列表
example_prompt=..., # 单条示例的格式模板
example_selector=None, # 可选:动态挑选示例(如语义相似)
)
| 参数 | 作用 |
|---|---|
| examples | 示例库,每条是 dict,字段名要和 example_prompt 里的 {变量} 对应 |
| example_prompt | 一条示例怎么排版(通常也是 ChatPromptTemplate.from_messages 建的) |
| example_selector | 不传则用全部 examples;传了可以按相似度只选几条 |
Few-shot Prompting(少样本提示)
原理:给 LLM 几个"输入 → 输出"的例子,让它照着模仿。
两种形式:
text
# Zero-shot(无示例)
用户:苹果是什么颜色?
AI:苹果有红色、绿色、黄色。
# Few-shot(有示例,输出更规范)
示例1 - 用户:香蕉是什么颜色?AI:香蕉通常是黄色。
示例2 - 用户:西瓜是什么颜色?AI:西瓜外皮绿色,果肉红色。
用户:苹果是什么颜色? ← 真实问题
AI:苹果通常是红色或绿色。 ← 自动模仿上面的简洁风格
LangChain 中用 FewShotChatMessagePromptTemplate 实现,它把示例列表自动展开成多轮 human/ai 对话消息塞进 prompt。
Chain-of-Thought(思维链,CoT)
原理:让模型在给答案前先写推理过程,就像让人"列草稿"。
实验证明:对数学/逻辑题,CoT 比直接问答准确率高 20-40%。
两种 CoT:
| 类型 | 方法 | 适用场景 |
|---|---|---|
| Zero-shot CoT | system 里写"请一步步思考" | 快速,不需要准备示例 |
| Few-shot CoT | 示例里包含完整推理步骤 | 更稳定,适合固定题型 |
text
# Zero-shot CoT 示例
system: "请先写推理步骤,再给答案"
user: "鸡兔同笼35头94脚"
# 模型会输出:
# 推理:设鸡x只,兔y只
# x + y = 35
# 2x + 4y = 94 → x=21, y=14
# 答案:鸡21只,兔14只
动态 Few-shot(SemanticSimilarityExampleSelector)
问题:示例库有100条,每次全塞进 prompt → token 暴增,费钱还慢。
解决:用向量相似度,自动从示例库里找最相关的 K 条塞进 prompt。
text
示例库(100条)
↓ 向量化存入 Qdrant
用户问"蓝莓什么颜色"
↓ 向量检索
命中最近邻:葡萄颜色、草莓颜色(都是水果颜色类问题)
↓
只把这2条塞进 prompt
关键类:SemanticSimilarityExampleSelector,内部封装了向量数据库的存储和检索。会向量化所有示例并创建存储
MessagesPlaceholder(消息占位符)
原理:在 prompt 模板中预留一个"插槽",运行时把任意消息列表插进去。
典型用途:多轮对话时注入历史记录。
text
# 模板定义时:
[system, <<chat_history占位>>, human当前问题]
# 运行时传入 chat_history:
[system, human"我叫小明", ai"你好小明", human"我叫什么?"]
# ↑ 这部分由 MessagesPlaceholder 动态填充
partial()------模板预填充
原理:提前固定模板中的部分变量,生成可复用的"专用模板"。
py
# 原始模板:两个变量
translate_prompt = "将{text}翻译成{language}"
# partial 固定 language,生成两个子模板
to_english = translate_prompt.partial(language="英文") # 只剩 text 变量
to_japanese = translate_prompt.partial(language="日文")
# 调用时只需传 text
to_english.invoke({"text": "你好"})
类比:像函数的 functools.partial,固定部分参数。
结构化输出(with_structured_output)
问题:LLM 返回的是字符串,需要手动解析 JSON,容易出错。
解决:用 Pydantic 定义期望格式,LangChain 自动让 LLM 返回符合格式的对象。
py
class MovieReview(BaseModel):
title: str
rating: float
pros: list[str]
structured_llm = llm.with_structured_output(MovieReview)
result = structured_llm.invoke("评价《星际穿越》")
# result 是 MovieReview 对象,直接 result.rating 取值
底层:LangChain 把 Pydantic schema 转成 OpenAI 的 function_call 或 json_schema,强制模型按格式输出。
「必要性」对照表
| 能力 | Chain 时代 | LangGraph 实战 | 是否值得学 |
|---|---|---|---|
| zero-shot / few-shot | 常用 | 常用(常在 node 里拼 prompt) | 必学 |
| SemanticSimilarityExampleSelector | 大示例库时常用 | 常用(可独立成 node) | 必学 |
| with_structured_output | 常用 | 非常常用(路由、评分、抽取) | 必学 |
| MessagesPlaceholder | 多轮 Chain 的标准写法 | 基本不用 | 了解即可 |
| partial() | 复用模板时有点用 | 很少用 | 了解即可 |
代码示例
py
# 准备示例库:每个 dict 包含 input(用户问题)和 output(期望回答)
FEW_SHOT_EXAMPLES = [
{
"input": "苹果是什么颜色?",
"output": "苹果通常是红色、绿色或黄色。",
},
]
cot_examples = [
{
"input": "一共有5只苹果,吃掉2只,还剩几只?",
"output": (
"推理:\n"
"1. 初始苹果数量 = 5\n"
"2. 吃掉 2 只\n"
"3. 剩余 = 5 - 2 = 3\n"
"答案:3"
),
},
]
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{output}"),
]
)
zero_shot_prompt
py
cot_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是数学解题助手。请先写出完整的推理步骤,最后单独一行写【答案:X】。",
),
("human", "{question}"),
]
)
chain = cot_prompt | llm
result = chain.invoke({"question": "鸡兔同笼:共35个头,94只脚,鸡和兔各有几只?"})
return result.content.strip()
few_shot_prompt
py
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{output}"),
]
)
few_shot_prompt = FewShotChatMessagePromptTemplate(
examples=cot_examples,
example_prompt=example_prompt,
)
# 调用方式如 zero_shot_prompt
SemanticSimilarityExampleSelector
py
selector = SemanticSimilarityExampleSelector.from_examples(
examples=FEW_SHOT_EXAMPLES,# 加入有很多例子
embeddings=embeddings,
vectorstore_cls=QdrantVectorStore,
location=":memory:",
collection_name="dynamic_few_shot_examples",
k=2, # 每次选最相似的2个示例
)
dynamic_few_shot = FewShotChatMessagePromptTemplate(
example_selector=selector,
example_prompt=example_prompt,
)
final_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是颜色描述助手,参考示例风格回答。"),
dynamic_few_shot,
("human", "{question}"),
]
)
# 调用方式如 zero_shot_prompt
MessagesPlaceholder
py
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个友好的助手,记住之前的对话内容。"),
(MessagesPlaceholder(variable_name="chat_history")),
("human", "{question}"),
]
)
chain = prompt | llm
history: list = []
q1 = "我叫小明,今年20岁。"
r1 = chain.invoke({"chat_history": history, "question": q1})
history.append(HumanMessage(content=q1))
history.append(AIMessage(content=r1.content))
partial_prompt
py
transalte_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是翻译助手,请将文本翻译成{language},只输出翻译结果。"),
("human", "{text}"),
]
)
to_endlish = transalte_prompt.partial(language="英文")
text = "人工智能正在改变世界。"
cn_result = cn_chain.invoke({"text": text}).content.strip()
return {"english": en_result, "chinese": cn_result}
with_structured_output
py
structured_llm = llm.with_structured_output(MovieReview, method="function_calling")
prompt = .ChatPromptTemplatefrom_messages(
[
("system", "你是专业影评人,请对电影进行结构化评价。"),
("human", "请评价电影《{movie}》"),
]
)
# 代码中 chain 等价于:
# messages = prompt.invoke({"movie": "东邪西毒"})只把模板渲染成消息,不会调用模型
# result = structured_llm.invoke(messages)
chain = prompt | structured_llm
result: MovieReview = chain.invoke({"movie": "东邪西毒"})
return result