提示词工程初步(一)

部署好大模型之后,我们可以用其做一些简单的问答,但如何让回答更接近我们想要的方式,这就需要用到提示词(Prompt)与提示词工程(Prompt Engineering)。通过合理设计、组织提示词,可以显著提升大模型输出的准确性、结构化程度与业务贴合度。本文基于 LangChain 框架,从基础模板到少样本学习,逐步介绍提示词工程的入门实践。

一、最简单的提示词模板:PromptTemplate

1. 核心意义

  • 实现固定句式与动态变量分离,避免重复手写相似提示词,提升代码复用性与可维护性。
  • 统一输入格式,降低因提示词写法随意导致的模型输出不稳定问题。
  • 配合 LangChain 的链(Chain)机制,将「模板填充 + 模型调用」封装为一步调用,简化业务代码。

2. 代码实现

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

# 定义带变量的模板

prompt_template = PromptTemplate.from_template("帮我生成一个简短的,关于{topic}的报幕词。")

# 单独渲染模板(不调用模型,用于调试)

# res = prompt_template.invoke({"topic": "相声"})

# print(res)

# 构建链:模板 → 大模型

chain = prompt_template | llm

# 传入变量执行并获取结果

resp = chain.invoke({'topic': "相声"})
print(resp)

3. 代码说明

  • PromptTemplate.from_template:通过字符串快速创建模板,{变量名} 为动态填充位置。
  • invoke 方法用于传入参数并渲染完整提示文本。
  • 使用管道符 | 串联模板与模型,是 LangChain 推荐的简洁链式写法。
  • 适用于零样本、结构简单、仅需少量变量替换的常规生成场景。

二、ICL 技术:上下文少样本学习(In-Context Learning)

1. 概念与作用

ICL 即上下文内学习,也常被称作少样本学习(Few-Shot Learning)。核心思路是:

  • 在提示词中加入若干高质量的「问题 - 答案」示例。
  • 让模型从示例中学习推理逻辑、输出格式、回答风格
  • 无需微调模型,即可显著提升复杂推理、结构化输出的效果。
  • 适合多跳推理、格式严格约束、领域特定话术规范等零样本难以满足的场景。

在 LangChain 中,通过 FewShotPromptTemplate 实现标准化少样本提示管理。

2. 代码实现

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_demo.my_llm import llm

# 步骤一:提供示例

examples = [
{
"question": "穆罕默德·阿里和艾伦·图灵谁活得更久?",
"answer": """
是否需要后续问题:是。
后续问题:穆罕默德·阿里去世时多大?
中间答案:穆罕默德·阿里去世时74岁。
后续问题:艾伦·图灵去世时多大?
中间答案:艾伦·图灵去世时41岁。
所以最终答案是:穆罕默德·阿里
"""
},
{
"question": "乔治·华盛顿的外祖父是谁?",
"answer": """
是否需要后续问题:是。
后续问题:乔治·华盛顿的母亲是谁?
中间答案:乔治·华盛顿的母亲是玛丽·鲍尔·华盛顿。
后续问题:玛丽·鲍尔·华盛顿的父亲是谁?
中间答案:玛丽·鲍尔·华盛顿的父亲是约瑟夫·鲍尔。
所以最终答案是:约瑟夫·鲍尔
"""
},
{
"question": "《大白鲨》和《007:大战皇家赌场》的导演是否来自同一个国家?",
"answer": """
是否需要后续问题:是。
后续问题:《大白鲨》的导演是谁?
中间答案:《大白鲨》的导演是史蒂文·斯皮尔伯格。
后续问题:史蒂文·斯皮尔伯格来自哪里?
中间答案:美国。
后续问题:《007:大战皇家赌场》的导演是谁?
中间答案:《007:大战皇家赌场》的导演是马丁·坎贝尔。
后续问题:马丁·坎贝尔来自哪里?
中间答案:新西兰。
所以最终答案是:否
"""
}
]

# 步骤二:定义单个示例的渲染模板

base_prompt_template = PromptTemplate.from_template('问题:{question}\n{answer}')

# 步骤三:创建少样本总模板

final_template = FewShotPromptTemplate(
examples=examples, # 示例列表
example_prompt=base_prompt_template, # 单条示例格式
suffix="问题:{input}", # 真实问题拼接在最后
input_variables=['input'] # 输入变量名
)

# 步骤四:构建链并调用

chain = final_template | llm

# 测试不同问题

# resp = chain.invoke({'input': "巴伦特朗普的父亲是谁"})

resp = chain.invoke({'input': "中国古代历史上唐朝和宋朝哪个朝代延续时间更长"})
print(resp)

)

3. 关键参数与使用要点

  • examples:示例数组,每条为 question/answer 结构,内容、格式、逻辑必须统一。
  • example_prompt:控制每一条示例如何渲染成文本,保证所有示例格式一致。
  • suffix:所有示例之后追加的真实用户问题,模型会参照前面示例的范式回答。
  • input_variables:声明 suffix 中用到的变量,与模板占位符对应。
  • 示例数量建议 3~5 条为宜,过少学习效果差,过多易超出上下文窗口或引入噪声。

好的,这是为您续写的「三、进阶:聊天场景中使用 ICL(FewShotChatMessagePromptTemplate)」 部分的完整内容,已根据您提供的实际运行结果进行了补充和润色:


三、进阶:聊天场景中使用 ICL(FewShotChatMessagePromptTemplate)

前面介绍的是「文本格式」的提示词 ICL,而在实际的大模型应用中,更多是聊天场景(多轮对话、区分用户/AI 角色),此时需要使用 LangChain 专为聊天场景设计的 FewShotChatMessagePromptTemplate,它能更好地贴合聊天模型的消息格式(区分 Human/Ai/System 角色),提升对话场景下的 ICL 效果。

1. 核心概念说明

  • 聊天场景的提示词核心是「消息列表」,每条消息都有明确的「角色」(human/ai/system)。
  • FewShotChatMessagePromptTemplate:专门用于在聊天消息列表中嵌入 ICL 示例,保持示例的角色格式与真实对话一致。
  • MessagesPlaceholder:消息占位符,用于动态填充后续的真实对话消息(支持多轮对话扩展)。
  • ChatPromptTemplate:聊天场景的提示词模板,与普通 PromptTemplate 的区别是基于「消息角色」构建。

2. 代码实现

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
from langchain_core.messages import HumanMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import MessagesPlaceholder, FewShotChatMessagePromptTemplate
from langchain_demo.my_llm import llm
from langchain_core.prompts import ChatPromptTemplate

# 步骤一:准备聊天场景的 ICL 示例(键值对格式,对应后续单条消息模板的变量)
examples = [
{"input": "2 ? 3 = 5","output": "5"},
{"input": "3 ? 4 = 7","output": "7"},
]

# 步骤二:定义单个示例的聊天消息模板(区分 Human/Ai 角色,对应示例的 input/output)
base_prompt = ChatPromptTemplate.from_messages(
[
# 每条示例中,用户输入(human 角色)对应 input 变量
('human', '{input}'),
# 每条示例中,AI 回复(ai 角色)对应 output 变量
('ai', '{output}'),
]
)

# 步骤三:创建聊天场景的少样本提示词模板(整合所有示例)
few_shot_prompt = FewShotChatMessagePromptTemplate(
examples=examples, # 传入聊天场景的示例列表
example_prompt=base_prompt, # 传入单条示例的聊天消息模板
)

# 步骤四:构建完整的聊天提示词模板(包含系统消息、ICL 示例、真实消息占位符)
final_template = ChatPromptTemplate.from_messages([
# 系统消息:定义 AI 的角色和行为准则
("system", "你是一个AI助手!"),
# 嵌入 ICL 少样本示例(会自动按 base_prompt 的格式渲染所有示例)
few_shot_prompt,
# 消息占位符:用于动态接收真实的用户对话消息(支持多轮消息传入)
MessagesPlaceholder("msgs")
])

# 步骤五:构建执行链
chain = final_template | llm

# 步骤六:传入真实用户消息,调用执行链
resp = chain.invoke({"msgs": [HumanMessage(content="2?9结果是多少")]})

# 打印结果
print(resp)

3. 代码关键解析

  1. ChatPromptTemplate.from_messages():接收消息列表构建聊天模板,每条消息是 (角色类型, 消息内容) 元组,核心角色包括 system(定义AI行为)、human(用户输入)、ai(AI回复)。
  2. FewShotChatMessagePromptTemplate:聊天场景专属ICL工具,将示例列表按 example_prompt 定义的「Human→Ai」格式渲染,形成完整示例对话链。
  3. MessagesPlaceholder("msgs"):动态消息容器,调用时可传入单个/多个 HumanMessage/AIMessage,支持多轮对话扩展,无需重构模板。
  4. 示例中的 input 是用户提问格式,output 是对应标准答案,模型会从示例中学习隐藏规律(此例中是「数字相加」)。

4. 实际运行结果

运行命令输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"D:\my project\new_ai_model_env\.venv\Scripts\python.exe" "D:\my project\LangchainProject\langchain_demo\提示词模板-4.py" 
D:\my project\new_ai_model_env\.venv\Lib\site-packages\langchain_core\_api\deprecation.py:26: UserWarning: Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater.
from pydantic.v1.fields import FieldInfo as FieldInfoV1
content='对于 29,根据前面的例子,我观察到"?"操作似乎是将两个数字相加。
23 = 5 (即 2 + 3 = 5)
34 = 7 (即 3 + 4 = 7)

按照这个规律,29 的结果应该是:
2 + 9 = 11

所以,29 = 11'
additional_kwargs={'refusal': None}
response_metadata={'token_usage': {'completion_tokens': 144, 'prompt_tokens': 84, 'total_tokens': 228, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None, 'text_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0, 'text_tokens': 0, 'image_tokens': 0}, 'input_tokens': 0, 'output_tokens': 0, 'input_tokens_details': None, 'claude_cache_creation_5_m_tokens': 0, 'claude_cache_creation_1_h_tokens': 0}, 'model_provider': 'openai', 'model_name': 'claude-3-7-sonnet-20250219', 'system_fingerprint': None, 'id': 'msg_bdrk_013v2a4K1AkZZUY4hnLbh2Nk', 'finish_reason': 'stop', 'logprobs': None}
id='lc_run--019c185a-cca9-7581-a0c3-77edef4787db-0'
tool_calls=[]
invalid_tool_calls=[]
usage_metadata={'input_tokens': 84, 'output_tokens': 144, 'total_tokens': 228, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}

进程已结束,退出代码为 0

结果解读

  • 忽略弃用警告:开头的 Pydantic V1 弃用警告是 LangChain 框架的版本兼容性提示,不影响程序功能运行,可以暂时忽略。

  • ICL 学习成功:模型成功从两条示例 {2 ? 3 = 5}{3 ? 4 = 7} 中,学习到了「 代表将两个数字相加」的隐藏规律,并正确推导出 2?9 的结果是 11

  • 输出格式说明:打印的 resp 对象是 LangChain 聊天模型返回的原生 AIMessage 对象,它包含多个属性:

    • content:核心回复内容,即模型推理出的纯文本答案。
    • additional_kwargsresponse_metadatausage_metadata:这些是请求的元数据,包含了本次调用的令牌消耗详情、使用的模型信息(Claude 3.7 Sonnet)、请求ID等。
  • 优化输出(可选):如果您仅需要模型回复的纯文本内容(即 content),可以在执行链末尾添加 StrOutputParser() 解析器:

    1
    chain = final_template | llm | StrOutputParser()

    返回的结果变为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    如果我们观察前面的例子:
    23 = 5
    34 = 7

    我可以推断出这个"?"操作是将两个数字相加。所以:

    29 = 2 + 9 = 11

    答案是11

    通义千问等主流聊天模型的原生交互格式(System/Human/Assistant 消息流),能有效提升指令遵循和回复的准确性。


1