跳到主要内容

Prompt 工程

Prompt 工程是构建有效 LLM 应用的核心技能。一个精心设计的提示词能够让模型输出更准确、更符合预期的结果。本章将详细介绍如何在 LangChain 中设计和优化提示词。

什么是 Prompt?

Prompt(提示词) 是输入给 LLM 的文本内容,它告诉模型要做什么、怎么做。Prompt 的质量直接影响模型的输出效果,好的 Prompt 能够让模型更准确地理解任务意图。

Prompt 的核心组成

一个完整的聊天 Prompt 通常包含三类消息:

  • System Message(系统消息):设定 AI 的角色、行为规范和基本规则,在整个对话中保持稳定
  • Human Message(人类消息):用户的输入和问题,代表具体的任务请求
  • AI Message(AI 消息):模型的历史回复,在多轮对话中保持上下文连贯

这种结构化的消息格式让模型能够更清晰地理解对话背景和当前任务,从而生成更相关的回答。

LangChain Prompt 组件

LangChain 在 langchain_core.prompts 模块中提供了完整的提示词工具链,支持从简单到复杂的各种场景。

1. ChatPromptTemplate

ChatPromptTemplate 是构建聊天模型提示词的核心类,它能够将消息模板化和参数化:

from langchain_core.prompts import ChatPromptTemplate

# 创建聊天模板 - 使用元组列表定义消息序列
template = ChatPromptTemplate([
("system", "你是一个专业的{field}专家,擅长用通俗易懂的语言解释复杂概念"),
("human", "请解释{concept}是什么?"),
("ai", "好的,我来详细解释..."),
("human", "那么在{scenario}场景中如何应用?")
])

# 填充变量生成消息列表
messages = template.format_messages(
field="Python编程",
concept="装饰器",
scenario="Web开发"
)

# messages 是一个消息对象列表,可以直接传给聊天模型
for msg in messages:
print(f"{msg.type}: {msg.content[:50]}...")

为什么使用 ChatPromptTemplate?

直接拼接字符串虽然简单,但存在几个问题:变量管理混乱、难以复用、无法处理复杂的消息结构。ChatPromptTemplate 提供了统一的接口,让提示词的管理更加规范。它支持变量插值、消息占位、部分填充等高级功能,是构建生产级 LLM 应用的基础。

2. PromptTemplate

对于非聊天模型(如文本补全模型),使用 PromptTemplate 创建简单的字符串模板:

from langchain_core.prompts import PromptTemplate

# 创建字符串模板
template = PromptTemplate.from_template(
"请用{language}编写一个函数,实现{function_name}功能。\n"
"要求:\n"
"1. 代码简洁易读\n"
"2. 添加必要注释\n"
"3. 处理边界情况"
)

# 填充模板
prompt = template.format(
language="Python",
function_name="冒泡排序"
)

print(prompt)

输出:

请用Python编写一个函数,实现冒泡排序功能。
要求:
1. 代码简洁易读
2. 添加必要注释
3. 处理边界情况

3. MessagesPlaceholder

在构建对话系统时,经常需要动态插入历史消息。MessagesPlaceholder 允许在模板中预留位置,运行时填充完整的消息列表:

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# 创建带历史消息占位符的模板
template = ChatPromptTemplate([
("system", "你是一个有帮助的AI助手,能够记住之前的对话内容"),
MessagesPlaceholder(variable_name="chat_history"), # 历史消息占位符
("human", "{question}") # 当前问题
])

# 使用时传入历史消息
messages = template.format_messages(
chat_history=[
("human", "什么是Python?"),
("ai", "Python是一种高级编程语言,以简洁易读著称"),
("human", "它有哪些特点?"),
("ai", "Python有动态类型、自动内存管理、丰富的标准库等特点"),
],
question="那我应该如何学习它?"
)

# 模型可以看到完整的对话历史,理解"它"指的是Python

MessagesPlaceholder 的 optional 参数

当历史消息可能为空时,设置 optional=True 避免报错:

template = ChatPromptTemplate([
("system", "你是一个AI助手"),
MessagesPlaceholder(variable_name="chat_history", optional=True),
("human", "{question}")
])

# 可以不传 chat_history
messages = template.format_messages(question="你好")

4. 简化语法

LangChain 提供了几种简化写法,让代码更简洁:

from langchain_core.prompts import ChatPromptTemplate

# 方式1:使用 from_messages(等同于直接传列表)
template = ChatPromptTemplate.from_messages([
("system", "你是{role}"),
("human", "{question}")
])

# 方式2:单条人类消息的快捷方式
template = ChatPromptTemplate.from_template("请解释{topic}")

# 方式3:占位符的简化语法
template = ChatPromptTemplate([
("system", "你是助手"),
("placeholder", "{chat_history}"), # 等同于 MessagesPlaceholder
("human", "{question}")
])

高级 Prompt 技术

1. 部分填充(Partial Variables)

当模板中有固定值或需要提前填充的变量时,使用 partial 创建新模板:

from langchain_core.prompts import ChatPromptTemplate

# 原始模板
template = ChatPromptTemplate([
("system", "你是{company}的客服代表,名字叫{name}"),
("human", "{question}")
])

# 部分填充公司信息(适用于同一公司多个客服)
partial_template = template.partial(company="智能科技", name="小智")

# 使用时只需填充问题
messages = partial_template.format_messages(question="你们的产品保修期多久?")

部分填充在以下场景特别有用:

  • 多个模板共享相同的系统配置
  • 某些变量需要从配置文件或环境变量读取
  • 构建模板库时提供默认值

2. 模板组合

复杂应用中,可以将多个小模板组合成大模板:

from langchain_core.prompts import ChatPromptTemplate

# 定义可复用的消息片段
system_prompt = ("system", """你是一个专业的技术文档助手。
回答规则:
1. 准确、简洁
2. 提供代码示例
3. 标注重要信息""")

# 创建不同的模板
python_template = ChatPromptTemplate([
system_prompt,
("human", "Python问题:{question}")
])

java_template = ChatPromptTemplate([
system_prompt,
("human", "Java问题:{question}")
])

3. Few-shot 提示

通过提供示例帮助模型理解任务格式:

from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate

# 定义示例
examples = [
{"input": "苹果", "output": "水果:苹果是一种常见的水果,富含维生素"},
{"input": "Python", "output": "编程语言:Python是一种高级编程语言,以简洁著称"},
{"input": "北京", "output": "城市:北京是中国的首都,政治文化中心"},
]

# 创建示例模板
example_template = ChatPromptTemplate([
("human", "{input}"),
("ai", "{output}")
])

# 创建 Few-shot 模板
few_shot_template = FewShotChatMessagePromptTemplate(
examples=examples,
example_prompt=example_template
)

# 组合成完整模板
full_template = ChatPromptTemplate([
("system", "你是一个百科助手,对输入的词汇进行分类并简要说明"),
few_shot_template,
("human", "{input}")
])

# 使用
messages = full_template.format_messages(input="ChatGPT")

Few-shot 提示的核心思想是通过示例让模型"看懂"任务模式,而不是通过冗长的描述。这种方法在格式化输出、特定风格生成等场景效果显著。

4. 示例选择器

当示例很多时,可以根据输入动态选择最相关的示例:

from langchain_core.prompts import FewShotChatMessagePromptTemplate, ChatPromptTemplate
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

# 大量示例库
examples = [
{"input": "悲伤", "output": "情绪:悲伤是一种负面情绪,通常在失去或失望时产生"},
{"input": "快乐", "output": "情绪:快乐是一种正面情绪,通常在成功或愉悦时产生"},
{"input": "Python", "output": "编程语言:Python是一种高级编程语言"},
{"input": "Java", "output": "编程语言:Java是一种面向对象编程语言"},
# ... 更多示例
]

# 创建语义相似度选择器
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples=examples,
embeddings=OpenAIEmbeddings(),
vectorstore_cls=Chroma,
k=2 # 选择最相关的2个示例
)

# 使用选择器创建模板
dynamic_template = FewShotChatMessagePromptTemplate(
example_selector=example_selector,
example_prompt=ChatPromptTemplate([
("human", "{input}"),
("ai", "{output}")
])
)

# 根据输入自动选择相关示例
full_template = ChatPromptTemplate([
("system", "你是一个百科助手"),
dynamic_template,
("human", "{input}")
])

语义相似度选择器会将输入与示例库进行匹配,选择语义最相近的示例。这样,当用户问"JavaScript"时,系统会优先选择"Python"和"Java"的示例,而不是"悲伤"和"快乐"。

5. 结构化输出提示

结合 Pydantic 强制模型输出结构化数据:

from langchain_core.prompts import ChatPromptTemplate
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

# 定义输出结构
class Product(BaseModel):
name: str = Field(description="产品名称")
price: float = Field(description="价格(元)")
features: list[str] = Field(description="产品特点列表")
recommendation: str = Field(description="购买建议")

# 创建解析器
parser = PydanticOutputParser(pydantic_object=Product)

# 创建模板(自动注入格式说明)
template = ChatPromptTemplate([
("system", "你是一个产品分析助手。{format_instructions}"),
("human", "分析这个产品:{product_name}")
])

# 部分填充格式说明
template = template.partial(
format_instructions=parser.get_format_instructions()
)

# 使用
messages = template.format_messages(product_name="iPhone 15")
# 模型会按照 Product 的结构输出

parser.get_format_instructions() 会生成类似这样的格式说明:

The output should be formatted as a JSON instance that conforms to the JSON schema below.

{
"name": "产品名称",
"price": "价格(元)",
"features": ["产品特点列表"],
"recommendation": "购买建议"
}

Prompt 设计原则

1. 清晰的任务定义

模型需要明确知道要做什么。模糊的指令会导致模糊的回答:

# 不够明确
bad_template = ChatPromptTemplate.from_template("讲讲{topic}")

# 清晰明确
good_template = ChatPromptTemplate.from_template("""
请解释{topic},包含以下内容:

1. **基本定义**:用一句话概括核心概念
2. **工作原理**:解释内部机制或流程
3. **实际应用**:举2-3个具体的使用场景
4. **代码示例**:提供简洁的示例代码
5. **注意事项**:列出常见错误或限制

目标读者:有基础编程经验的开发者
""")

2. 提供充分上下文

上下文帮助模型理解问题的背景和约束:

# 缺少上下文
bad_template = ChatPromptTemplate.from_template("写一个排序函数")

# 提供完整上下文
good_template = ChatPromptTemplate.from_template("""
背景:开发一个电商平台的后台管理系统,需要对订单列表进行排序

需求:
- 支持按订单金额、下单时间、客户等级排序
- 支持升序和降序
- 处理空值情况(空值排在最后)

技术栈:Python 3.10+
性能要求:订单量约 10 万条,排序响应时间小于 100ms

请提供完整的函数实现
""")

3. 使用分隔符

清晰分隔不同部分的内容,避免模型混淆:

template = ChatPromptTemplate.from_template("""
请分析以下文档并回答问题:

文档内容:
{document}

问题:
{question}

请先总结文档主要内容,然后回答问题。
""")

分隔符(如 XML 标签、特殊字符)让模型清晰区分指令、数据和输出格式,减少混淆。

4. 迭代优化

Prompt 设计是一个迭代过程,需要不断测试和优化:

# 版本管理
PROMPTS = {
"v1": "解释{topic}",
"v2": "请详细解释{topic},包含定义、原理和应用",
"v3": "作为专家,解释{topic}...\n\n要求:\n1. ...\n2. ...",
}

# A/B 测试
def test_prompt_version(version, test_cases):
results = []
template = ChatPromptTemplate.from_template(PROMPTS[version])

for case in test_cases:
response = chain.invoke({"topic": case["topic"]})
results.append({
"input": case["topic"],
"output": response,
"score": evaluate_response(response, case["expected"])
})

return results

LCEL 中的 Prompt 使用

在 LangChain Expression Language (LCEL) 中,Prompt 模板作为 Runnable 组件参与链式调用:

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain.output_parsers import StrOutputParser

# 初始化
model = init_chat_model("openai:gpt-4o-mini")

# 使用 LCEL 组合
chain = ChatPromptTemplate.from_template("解释{topic}") | model | StrOutputParser()

# 调用
result = chain.invoke({"topic": "Python装饰器"})

LCEL 让 Prompt、模型、解析器能够通过管道操作符 | 组合,形成声明式的数据处理流程。这种方式代码简洁,且自动支持流式输出、批处理、异步调用等高级功能。

流式输出示例

# 流式输出
for chunk in chain.stream({"topic": "Python装饰器"}):
print(chunk, end="", flush=True)

批处理示例

# 批量处理
results = chain.batch([
{"topic": "装饰器"},
{"topic": "生成器"},
{"topic": "上下文管理器"}
])

常见问题与解决方案

1. 变量未填充错误

KeyError: 'variable_name'

确保所有模板变量都被填充:

# 检查模板需要哪些变量
print(template.input_variables) # ['field', 'concept', 'scenario']

# 确保传入所有变量
messages = template.format_messages(
field="Python",
concept="装饰器",
scenario="Web开发"
)

2. 特殊字符处理

模板中使用 {} 字符时需要转义:

# 错误:会尝试解析 {name} 变量
template = PromptTemplate.from_template("输出格式: {name: string}")

# 正确:双大括号转义
template = PromptTemplate.from_template("输出格式: {{name: string}}")

3. 消息类型问题

确保消息类型正确:

# 正确的消息类型
ChatPromptTemplate([
("system", "..."), # 系统消息
("human", "..."), # 人类消息
("ai", "..."), # AI 消息
("placeholder", "..."), # 占位符
])

# 错误:使用不支持的消息类型
# ChatPromptTemplate([("user", "...")]) # 应该是 "human"

最佳实践总结

  1. 使用 ChatPromptTemplate 处理聊天模型,它的结构化消息格式更符合现代 LLM 的输入要求

  2. 系统消息设置角色和规则,让模型有明确的行为边界

  3. 使用 MessagesPlaceholder 管理对话历史,实现多轮对话的上下文保持

  4. Few-shot 提示提高一致性,通过示例让模型理解输出格式

  5. 结合输出解析器,强制结构化输出,便于后续处理

  6. 迭代测试和优化,Prompt 效果需要通过实际测试验证

下一步

现在你已经掌握了 Prompt 工程的核心技巧,接下来学习: