从零开始学LangChain(三):Prompts 提示词工程

从零开始学LangChain(三):Prompts 提示词工程

本系列教程将带你从零开始学习LangChain框架,构建强大的AI应用程序。

什么是提示词工程?

提示词工程(Prompt Engineering)是指设计和优化输入给大语言模型的文本,以获得更准确、更符合预期的输出。

想象一下,LLM就像一个非常聪明但需要明确指令的助手。如果你给的指令模糊不清,输出的结果可能不尽如人意。而好的提示词能让LLM准确理解你的意图,给出高质量的回复。

提示词有五个核心要素需要关注。角色设定告诉AI扮演什么角色,比如”你是一位资深Python工程师”。任务描述清晰说明要完成什么,比如”请解释什么是装饰器”。输入数据提供必要的上下文信息,比如具体的代码片段。输出格式指定期望的形式,比如”请用JSON格式返回”。约束条件限制输出的范围或方式,比如”不超过100字”。

提示词模板基础

LangChain提供了强大的提示词模板功能,让提示词的管理和复用变得简单。

PromptTemplate

最基础的提示词模板类是PromptTemplate。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from langchain.prompts import PromptTemplate

# 创建模板
template = """
请解释以下{language}概念:{concept}
要求:
1. 用简单的比喻
2. 提供一个代码示例
3. 字数不超过{word_limit}字
"""

prompt = PromptTemplate(
template=template,
input_variables=["language", "concept", "word_limit"]
)

# 格式化提示词
formatted_prompt = prompt.format(
language="Python",
concept="装饰器",
word_limit=200
)

print(formatted_prompt)

更简洁的方式是使用from_template。

1
2
3
4
5
6
7
8
9
10
11
12
13
from langchain.prompts import PromptTemplate

# 更简洁的创建方式
prompt = PromptTemplate.from_template(
"请用{style}风格解释{topic},不超过{max_words}字"
)

# 使用
prompt_input = prompt.format(
style="幽默",
topic="递归",
max_words=100
)

ChatPromptTemplate

专为对话场景设计的提示词模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from langchain.prompts import ChatPromptTemplate

# 创建聊天提示词模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一位资深的{role},擅长用{method}教学。"),
("human", "请解释:{topic}")
])

# 格式化
messages = prompt.format_messages(
role="Python导师",
method="比喻",
topic="生成器"
)

print(messages)

也可以创建更复杂的对话模板,包含对话历史占位符。

1
2
3
4
5
6
7
8
9
10
from langchain.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
("system", "你是一位专业的客服代表。"),
("placeholder", "{chat_history}"), # 占位符,用于填充对话历史
("human", "{user_input}"),
("messages", "additional", [ # 可选的额外消息
("ai", "请问还有什么可以帮您的吗?")
])
])

少样本提示

给模型提供示例,让它理解你想要的输出格式,这被称为少样本提示(Few-shot prompting)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from langchain.prompts import FewShotPromptTemplate
from langchain.prompts.prompt import PromptTemplate

# 定义示例
examples = [
{
"question": "苹果是什么颜色的?",
"answer": "苹果通常是红色、绿色或黄色的。"
},
{
"question": "天空是什么颜色的?",
"answer": "晴朗的天空是蓝色的。"
}
]

# 创建示例提示词模板
example_prompt = PromptTemplate(
input_variables=["question", "answer"],
template="问题:{question}\n答案:{answer}\n"
)

# 创建少样本提示词模板
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
prefix="以下是一些问答示例:\n\n",
suffix="\n\n现在请回答:\n问题:{input}",
input_variables=["input"],
example_separator="\n"
)

# 使用
final_prompt = few_shot_prompt.format(input="香蕉是什么颜色的?")
print(final_prompt)

还可以动态选择示例,根据输入长度自动选择最相关的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from langchain.prompts import FewShotPromptTemplate
from langchain.prompts.example_selector import LengthBasedExampleSelector

# 准备更多示例
all_examples = [
{"input": "2+2=?", "output": "4"},
{"input": "3*3=?", "output": "9"},
{"input": "10/2=?", "output": "5"},
{"input": "5+5=?", "output": "10"},
]

# 创建示例选择器
example_selector = LengthBasedExampleSelector(
examples=all_examples,
example_prompt=PromptTemplate(
input_variables=["input", "output"],
template="输入:{input} 输出:{output}"
),
max_length=20 # 根据输入长度动态选择最合适的示例
)

# 使用选择器
dynamic_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=PromptTemplate(
input_variables=["input", "output"],
template="输入:{input} 输出:{output}"
),
suffix="\n输入:{input} 输出:",
prefix="数学计算示例:",
input_variables=["input"]
)

# 根据输入动态选择示例
print(dynamic_prompt.format(input="100/10=?"))

部分变量填充

有时候你想先填充部分变量,稍后再填充剩余变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from langchain.prompts import PromptTemplate

# 创建包含多个变量的模板
prompt = PromptTemplate(
template="请用{language}编写一个{task},要求:{requirements}",
input_variables=["language", "task", "requirements"]
)

# 部分填充
partial_prompt = prompt.partial(
language="Python",
task="计算斐波那契数列的函数"
)

# 稍后填充剩余变量
final_prompt = partial_prompt.format(
requirements="使用递归实现,添加类型注解"
)

print(final_prompt)

还可以使用函数进行部分填充,每次调用都会获取新的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from datetime import datetime

def get_current_date():
"""获取当前日期"""
return datetime.now().strftime("%Y-%m-%d")

# 创建模板
prompt = PromptTemplate(
template="今天是{date},请生成一份{report_type}报告。",
input_variables=["date", "report_type"]
)

# 使用函数进行部分填充
partial_prompt = prompt.partial(
date=get_current_date
)

# 每次调用都会获取新的日期
print(partial_prompt.format(report_type="销售"))

输出解析器

控制LLM的输出格式,使其更易于程序处理。

CSV解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from langchain.prompts import PromptTemplate
from langchain.output_parsers import CommaSeparatedListOutputParser

# 创建解析器
parser = CommaSeparatedListOutputParser()

# 获取格式指令
format_instructions = parser.get_format_instructions()

# 创建提示词
prompt = PromptTemplate(
template="列出5个{topic}。\n{format_instructions}",
input_variables=["topic"],
partial_variables={"format_instructions": format_instructions}
)

# 使用
input_prompt = prompt.format(topic="Python Web框架")
print(input_prompt)

# 假设模型输出:"Django, Flask, FastAPI, Tornado, Pyramid"
parsed_output = parser.parse("Django, Flask, FastAPI, Tornado, Pyramid")
print(parsed_output)

结构化输出解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from langchain.prompts import PromptTemplate
from langchain.output_parsers import StructuredOutputParser
from pydantic import BaseModel, Field

# 定义输出结构
class MovieReview(BaseModel):
title: str = Field(description="电影标题")
rating: float = Field(description="评分(0-10)")
summary: str = Field(description="剧情简介")
pros: list[str] = Field(description="优点列表")
cons: list[str] = Field(description="缺点列表")

# 创建解析器
parser = StructuredOutputParser.from_pydantic(MovieReview)

# 创建提示词
prompt = PromptTemplate(
template="请对电影《{movie_name}》进行评价。\n{format_instructions}",
input_variables=["movie_name"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 使用
formatted_prompt = prompt.format(movie_name="肖申克的救赎")
print(formatted_prompt)

提示词优化技巧

明确角色和任务

清晰的定义比模糊的指令效果更好。

1
2
3
4
5
6
7
8
9
10
11
# ❌ 不够明确
bad_prompt = "写一个排序算法"

# ✅ 清晰的角色和任务
good_prompt = """
你是一位计算机科学教授,擅长用通俗易懂的方式解释算法。
请用Python写一个快速排序算法,要求:
1. 添加详细的注释
2. 解释算法的时间复杂度
3. 提供一个使用示例
"""

提供上下文信息

完整的上下文信息帮助模型更好理解你的需求。

1
2
3
4
5
6
# ❌ 缺少上下文
bad_prompt = "这段代码有什么问题?\n代码:x = [1, 2, 3]"

# ✅ 提供完整上下文
good_prompt = """
以下代码试图从列表中移除偶数,但运行结果不正确:

x = [1, 2, 3, 4, 5]
for i in x:
if i % 2 == 0:
x.remove(i)
print(x) # 期望输出 [1, 3, 5],实际输出 [1, 3, 4]

1
2
3

请分析问题原因并提供正确的解决方案。
"""

使用思维链

让模型逐步推理,得到更准确的答案。

1
2
3
4
5
6
7
8
9
10
11
12
# 让模型逐步推理
chain_prompt = """
请解答以下问题,要求一步步思考:

问题:一个农场有鸡和兔子共35只,腿共94条,问鸡和兔子各多少只?

请按以下步骤回答:
1. 设未知数
2. 列出方程
3. 解方程
4. 验证答案
"""

实战示例:智能提示词生成器

创建一个工具,帮助生成高质量的提示词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

class PromptOptimizer:
"""提示词优化器"""

def __init__(self):
self.chat = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.3)

def optimize_prompt(
self,
original_prompt: str,
context: str = "",
requirements: str = ""
) -> str:
"""优化提示词"""

system_msg = """你是一位提示词工程专家,擅长将简单的需求转换为高质量的提示词。
优化后的提示词应该:
1. 角色设定清晰
2. 任务描述明确
3. 包含必要的约束条件
4. 提供输出格式要求"""

user_msg = f"""原始提示词:{original_prompt}

上下文信息:{context}
特殊要求:{requirements}

请优化这个提示词,使其更加有效。"""

prompt = ChatPromptTemplate.from_messages([
("system", system_msg),
("human", user_msg)
])

chain = prompt | self.chat
return chain.invoke({}).content

# 使用示例
optimizer = PromptOptimizer()

original = "写代码"
context = "需要实现一个用户登录功能"
requirements = "使用Flask框架,包含密码哈希"

optimized = optimizer.optimize_prompt(original, context, requirements)
print("=== 优化后的提示词 ===")
print(optimized)

常见错误

变量名不匹配

症状是出现KeyError: 'missing_input_variable'错误。解决方案是确保模板中的变量名与format时使用的变量名一致。

1
2
3
4
5
6
7
8
from langchain.prompts import PromptTemplate

# ❌ 模板中有{topic},但format时使用了{subject}
prompt = PromptTemplate.from_template("解释{topic}")
prompt.format(subject="Python") # 错误!

# ✅ 变量名要匹配
prompt.format(topic="Python") # 正确

忘记转义大括号

症状是出现ValueError: Invalid interpolation错误。解决方案是使用双大括号转义,或者使用jinja2模板格式。

1
2
3
4
5
6
7
8
9
10
11
# ❌ 未转义
template = "使用{{变量}}" # 会被当作变量

# ✅ 使用双大括号转义
template = "使用{{{prefix}_变量}}" # 输出:{prefix}_变量

# 或使用template_format="jinja2"
prompt = PromptTemplate(
template="使用 {{ 变量 }}",
template_format="jinja2"
)

系列导航