第1章:提示工程与上下文学习 (Prompt Engineering & ICL)
"In-Context Learning is meta-learning without gradient descent." ------ 上下文学习本质上是一种无需梯度更新的元学习。本章将深入探讨如何在不更新模型参数的情况下,通过提示工程(Prompt Engineering)和上下文学习(In-Context Learning)激发大模型的潜能,构建复杂的应用系统。
目录
- 第一节:提示工程最佳实践
- [1.1 结构化提示词 (Structured Prompt)](#1.1 结构化提示词 (Structured Prompt))
- [1.2 角色与约束 (Role & Constraints)](#1.2 角色与约束 (Role & Constraints))
- [1.3 输出控制 (Output Format)](#1.3 输出控制 (Output Format))
- 第二节:上下文学习 (In-Context Learning)
- [2.1 Few-Shot Learning 原理](#2.1 Few-Shot Learning 原理)
- [2.2 动态示例选择 (Dynamic Few-Shot)](#2.2 动态示例选择 (Dynamic Few-Shot))
- [2.3 实战:构建 Few-Shot 文本分类器](#2.3 实战:构建 Few-Shot 文本分类器)
- 第三节:思维链推理 (Chain-of-Thought)
- [3.1 Zero-Shot CoT](#3.1 Zero-Shot CoT)
- [3.2 Manual CoT](#3.2 Manual CoT)
- [3.3 Least-to-Most Prompting](#3.3 Least-to-Most Prompting)
- [第四节:RAG 系统设计模式预览](#第四节:RAG 系统设计模式预览)
- [4.1 为什么需要 RAG?](#4.1 为什么需要 RAG?)
- [4.2 基础 RAG 流程](#4.2 基础 RAG 流程)
- [4.3 模块化 RAG 架构](#4.3 模块化 RAG 架构)
- 第五节:实战:从零构建智能对话系统
- [5.1 系统架构设计](#5.1 系统架构设计)
- [5.2 核心 Prompt 编排](#5.2 核心 Prompt 编排)
- [5.3 完整代码实现](#5.3 完整代码实现)
- [第六节:进阶应用:SetFit 与 语义聚类](#第六节:进阶应用:SetFit 与 语义聚类)
- [6.1 SetFit:少样本分类微调](#6.1 SetFit:少样本分类微调)
- [6.2 BERTopic:语义主题建模](#6.2 BERTopic:语义主题建模)
- 本章小结
- 思考练习
- 参考资料
第一节:提示工程最佳实践
提示工程(Prompt Engineering)并非玄学,而是与模型沟通的编程语言。SOTA 的提示词设计通常遵循清晰的结构化原则。
1.1 结构化提示词 (Structured Prompt)
一个优秀的 Prompt 应该像代码一样具备模块化结构,通常包含以下要素:
- Role (角色):定义 AI 的身份和能力边界。
- Context (背景):提供任务背景信息。
- Instruction (指令):清晰、动词导向的任务描述。
- Data (数据):输入的数据内容。
- Output Indicator (输出指引):期望的输出格式。
代码示例:
python
PROMPT_TEMPLATE = """
### Role
你是一位资深的数据分析师,擅长从非结构化文本中提取关键商业洞察。
### Context
我们收到了一批用户关于"智能咖啡机"的产品反馈,需要整理用户的核心痛点。
### Instruction
请分析以下用户评论,提取出:
1. 情感倾向 (Positive/Negative/Neutral)
2. 核心关键词 (最多3个)
3. 问题摘要 (一句话)
### Data
用户评论:"{user_review}"
### Output Format
请仅输出 JSON 格式,不要包含Markdown标记:
{{
"sentiment": "...",
"keywords": ["...", "..."],
"summary": "..."
}}
"""
1.2 角色与约束 (Role & Constraints)
角色设定不仅是"扮演游戏",它实际上是在潜在空间(Latent Space)中锁定模型的生成模式。
- 弱角色:"帮我写个代码。"
- 强角色:"你是一位 Google L5 级别的 Python 工程师,遵循 PEP8 规范,代码需包含类型提示(Type Hints)和 Google 风格的 Docstring。"
约束技巧:
- 负向约束 (Negative Constraints):明确告诉模型不要做什么(例如:"不要使用礼貌用语"、"不要解释代码")。
- 长度约束:指定字数或段落数。
1.3 输出控制 (Output Format)
在工程化应用中,稳定的输出格式至关重要。
- JSON Mode :现代 LLM(如 GPT-4o, DeepSeek-V3)通常支持
response_format={"type": "json_object"}。 - Structure Prompting:在 Prompt 末尾给出明确的 Schema 定义。
python
# 使用 Pydantic 定义输出结构 (配合 Instructor 库)
from pydantic import BaseModel
from typing import List
class AnalysisResult(BaseModel):
sentiment: str
keywords: List[str]
summary: str
# 这种方式能确保 100% 的格式稳定性
第二节:上下文学习 (In-Context Learning)
核心问题 :如何不重新训练模型,就能让它理解复杂任务?
答案:In-Context Learning (ICL)。利用模型强大的短期记忆(Context Window),直接在 Prompt 中提供示例。
2.1 Few-Shot Learning 原理
Few-Shot Learning(少样本学习)是指在 Prompt 中提供 Input-Output 对 作为示例。
为什么有效?
模型通过注意力机制(Self-Attention)"读取"这些示例,捕捉输入与输出之间的映射关系,并在推理时模仿这种模式。这本质上是一种无需梯度更新的元学习。
示例对比:
-
Zero-Shot:
text这句评论是正面的还是负面的? "快递慢得像乌龟。" -
Few-Shot:
text判断评论情感: 输入:"屏幕清晰度很高。" 输出:正面 输入:"电池用了半天就没电了。" 输出:负面 输入:"快递慢得像乌龟。" 输出:
2.2 动态示例选择 (Dynamic Few-Shot)
当任务复杂且示例池很大时,固定示例效果不佳。最佳实践是根据 Query 动态检索最相似的示例。
架构设计:
- 建立一个 示例库 (Example Store) (Input-Output Pairs)。
- 为示例库中的 Input 计算 Embeddings,存入向量库。
- 用户输入 Query 时,先检索 Top-K 最相似的 Input 示例。
- 将这 K 个示例组装进 Prompt。
代码逻辑:
python
from sentence_transformers import SentenceTransformer, util
embedder = SentenceTransformer('BAAI/bge-large-zh-v1.5')
# 示例库: 输入-输出对
example_corpus = [
"屏幕清晰度很高。",
"电池用了半天就没电了。",
"物流速度超快!"
]
example_labels = ["正面", "负面", "正面"]
example_embeddings = embedder.encode(example_corpus)
def get_dynamic_prompt(query):
query_emb = embedder.encode(query)
# 检索最相似的 3 个示例
hits = util.semantic_search(query_emb, example_embeddings, top_k=3)
prompt = "参考以下相似案例进行回答:\n\n"
for hit in hits[0]:
idx = hit['corpus_id']
prompt += f"示例输入:{example_corpus[idx]}\n示例输出:{example_labels[idx]}\n\n"
prompt += f"当前输入:{query}\n输出:"
return prompt
2.3 实战:构建 Few-Shot 文本分类器
利用 ICL,我们可以快速构建一个高精度的意图分类器,无需任何训练。
(此部分整合了原章节的分类实战内容,但侧重于 Prompt 实现)
python
# 核心 Prompt 模板
CLASSIFICATION_PROMPT = """
你是一个智能客服意图识别助手。请参考以下示例,确定用户问题的类别。
类别列表:[账号问题, 支付失败, 物流查询, 售后退换]
示例 1:
用户: "怎么还没发货?都三天了"
类别: 物流查询
示例 2:
用户: "充值成功了但是余额没变"
类别: 支付失败
示例 3:
用户: "我想修改绑定的手机号"
类别: 账号问题
用户: "{query}"
类别:
"""
第三节:思维链推理 (Chain-of-Thought)
3.1 为什么需要 CoT?(数学视角)
在直觉上,CoT (Chain-of-Thought) 是让模型"慢下来思考"。但在数学上,它的本质是引入了隐变量 (Latent Variable) 来解构复杂概率分布。
1. 贝叶斯视角 :
对于复杂问题(如数学题),直接建模 P ( a n s w e r ∣ q u e s t i o n ) P(answer|question) P(answer∣question) 是非常困难的,因为输入空间到输出空间的映射极其非线性。
CoT 引入了中间推理步骤 z z z (rationale):
P ( a ∣ q ) = ∑ z P ( a ∣ z , q ) ⋅ P ( z ∣ q ) P(a|q) = \sum_{z} P(a|z, q) \cdot P(z|q) P(a∣q)=z∑P(a∣z,q)⋅P(z∣q)
其中:
- P ( z ∣ q ) P(z|q) P(z∣q):给定问题,生成推理步骤的概率(这一步往往更符合自然语言逻辑,容易建模)。
- P ( a ∣ z , q ) P(a|z, q) P(a∣z,q):给定推理步骤,生成答案的概率(这一步通常是确定性的)。
2. 计算复杂度视角 :
Transformer 模型的计算深度(层数)是固定的。对于需要 N N N 步逻辑推理的问题,如果只输出一个 token 的答案,模型必须在有限的层数内完成所有计算。
CoT 允许模型生成 T T T 个 token 的推理过程,这相当于将计算时间线性扩展,用更多的 FLOPs (浮点运算) 换取更高的准确率。
3.2 经典 CoT 模式
(1) Zero-Shot CoT
最著名的"魔法咒语":
"Let's think step by step." (让我们一步步思考)
这句话会显著改变模型的生成概率分布,使其倾向于输出逻辑连接词(如 "First", "Therefore"),从而触发内部的推理电路。
(2) Manual CoT (Few-Shot)
在 Few-Shot 示例中,显式写出推理过程。
示例:
text
问题:Roger 有 5 个网球,他又买了两罐网球,每罐有 3 个。他现在一共有多少个网球?
思考过程:
1. Roger 起始有 5 个球。
2. 2 罐网球,每罐 3 个,共 2 * 3 = 6 个球。
3. 总数为 5 + 6 = 11 个。
答案:11
3.3 CoT 的缺陷与改进
1. 缺陷 A:错误级联 (Error Cascading)
由于 P ( a ∣ z ) P(a|z) P(a∣z) 强依赖于 z z z,如果推理链中的某一步 z t z_t zt 出现幻觉,后续的所有推理都会基于这个错误前提。
2. 缺陷 B:事后合理化 (Post-hoc Rationalization)
这是一种更隐蔽的缺陷,揭示了理论理想与实践现实的差距。
- 理论模型 (贝叶斯视角):CoT 应该遵循 P ( a ∣ z , q ) ⋅ P ( z ∣ q ) P(a|z,q) \cdot P(z|q) P(a∣z,q)⋅P(z∣q) 的链式推理,即先生成推理步骤 z z z,再基于 z z z 推导答案 a a a。
- 实践现实 (Post-hoc):模型可能因为训练数据中存在某种"捷径",直接通过 P ( a ∣ q ) P(a|q) P(a∣q) 先"蒙"出了正确答案,然后为了满足 CoT 格式的要求,事后编造一段看似合理的推理过程。
现象 :推理过程 z z z 充满错误逻辑,但最终答案 a a a 竟然是对的。此时 CoT 的数学分解失效,变成了"马后炮"。这说明贝叶斯分解是 CoT 的理想工作机制,但在实际应用中,模型并不总是严格遵循这一机制。
3. 改进方案:Self-Consistency (自洽性)
利用温度采样(Temperature > 0)生成 K K K 条不同的推理路径,然后对最终答案进行投票 (Majority Vote) 。
a ^ = arg max a ∑ k = 1 K I ( a k = a ) \hat{a} = \arg\max_{a} \sum_{k=1}^K \mathbb{I}(a_k = a) a^=argamaxk=1∑KI(ak=a)
这利用了大数定律消除了单条推理路径的随机噪声。
3. 进阶结构:Tree of Thoughts (ToT)
将线性的 CoT 扩展为树状结构,允许模型在推理过程中:
- 分支:探索多种可能性。
- 回溯:如果当前路径不可行,退回上一步。
- 评估:对每一步的状态进行自我打分。
3.4 Least-to-Most Prompting
对于极度复杂的问题,采用**"拆解-解决"**策略:
- Decomposition: 先让模型把大问题拆解为子问题列表。
- Sequential Solving: 逐个解决子问题,把上一步的答案作为下一步的输入。
从推理到检索:CoT 的局限性
CoT 强化了模型的推理能力,但它依然无法解决一个根本问题:知识的边界。无论推理链多么完善,如果模型的参数中没有存储相关知识(例如最新的市场数据、企业内部文档),它只能基于"幻觉"进行推理。
这就引出了下一个核心问题:如何让模型访问外部知识? 这正是 RAG (Retrieval-Augmented Generation) 的使命------将模型的生成能力与外部知识库的检索能力相结合。
第四节:RAG 系统设计模式预览
虽然 RAG (Retrieval-Augmented Generation) 也是提示工程的一种延伸(将检索结果作为 Context),但它已发展为独立领域。本节简要预览,详细内容见下一章。
4.1 为什么需要 RAG?
- 幻觉 (Hallucination):模型会一本正经地胡说八道。
- 时效性 (Cutoff Date):模型知识有截止日期(如 2023 年)。
- 私有数据 (Private Data):模型不知道企业内部文档。
4.2 基础 RAG 流程与代码
Query -> Search(Vector DB) -> Context -> Augmented Prompt -> LLM -> Answer
极简代码示例:
python
# 1. 检索 (Retrieve)
docs = vector_db.similarity_search("公司Q3营收", k=3)
context = "\n".join([d.page_content for d in docs])
# 2. 增强 (Augment)
prompt = f"基于以下上下文回答问题:\n{context}\n\n问题:公司Q3营收是多少?"
# 3. 生成 (Generate)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)
4.3 模块化 RAG 架构
- RRR 模式:Rewrite (改写问题) -> Retrieve (检索) -> Read (阅读回答)。
- HyDE:先假设一个答案,用假设答案去检索,再生成真实答案。
第五节:实战:从零构建智能对话系统
本节我们将综合运用 Role, Few-Shot, CoT 技术,构建一个基于文档的智能问答助手。
5.1 系统架构设计
系统分为三层:
- 输入处理层:Prompt 优化、意图识别。
- 上下文层:管理对话历史 (Memory)、检索知识库。
- 生成层:调用 LLM API,结构化输出。
5.2 核心 Prompt 编排
python
SYSTEM_PROMPT = """
Role:
你是一个专业的金融文档助手。你的任务是依据提供的上下文(Context)回答用户关于财报的问题。
Constraints:
1. 只能基于 Context 回答,不要使用你的外部知识。
2. 如果 Context 中没有答案,请直接回答"根据当前文档无法回答",不要编造。
3. 保持客观、专业,引用 Context 中的数据时要保留 2 位小数。
Thinking Process (CoT):
请先分析用户的意图,然后在 Context 中寻找相关段落,最后整合成答案。
"""
5.3 完整代码实现
python
from openai import OpenAI
client = OpenAI()
def chat_bot(user_query, context_chunks):
# 1. 构建 Context 字符串
context_str = "\n---\n".join(context_chunks)
# 2. 组装 Prompt
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Context:\n{context_str}\n\nQuestion: {user_query}"}
]
# 3. 调用 LLM
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0.3 # 降低随机性,提高事实准确度
)
return response.choices[0].message.content
# 模拟运行
docs = [
"Q3财报显示,公司营收达到 10.52 亿元,同比增长 15.3%。",
"净利润为 2.1 亿元,主要得益于云服务业务的扩展。"
]
print(chat_bot("公司Q3营收表现如何?", docs))
第六节:进阶应用:SetFit 与 语义聚类
为了弥补传统 Prompt 在小样本高精度场景下的不足,我们可以引入更重的工程方案。这是连接 Prompt Engineering 与 Fine-tuning 的桥梁。
(本节保留了原"文本分类"章节的精华内容,作为高级实战案例)
6.1 SetFit:少样本分类微调
SetFit (Sentence Transformer Fine-tuning) 是一种无需大规模标注数据的高效分类框架。
- 原理:先对 Embedding 模型进行对比学习微调(Contrastive Learning),再训练一个分类头(Classification Head)。
- 优势:在只有 8 个样本/类的情况下,性能可媲美全量微调的 BERT。
python
from setfit import SetFitModel, SetFitTrainer
from datasets import load_dataset
# 1. 加载少样本数据 (每类仅需8条)
dataset = load_dataset("SetFit/emotion", split="train[:32]") # 示例
# 2. 初始化模型
model = SetFitModel.from_pretrained("sentence-transformers/paraphrase-mpnet-base-v2")
# 3. 训练 (极快, CPU上几分钟)
trainer = SetFitTrainer(
model=model,
train_dataset=dataset,
loss_class="CosFaceLoss",
metric="accuracy",
batch_size=16,
num_iterations=20, # 对比学习迭代次数
)
trainer.train()
# 4. 推理
preds = model(["This movie is so boring..."])
print(preds)
6.2 BERTopic:语义主题建模
当没有标签时,如何理解大规模文本数据?Prompt Engineering 很难处理全量数据聚类,这时需要 BERTopic。
代码实战:
python
from bertopic import BERTopic
from sklearn.datasets import fetch_20newsgroups
# 1. 加载数据
docs = fetch_20newsgroups(subset='all')['data'][:1000]
# 2. 训练模型 (Embed -> UMAP -> HDBSCAN -> c-TF-IDF)
topic_model = BERTopic(language="english", calculate_probabilities=True)
topics, probs = topic_model.fit_transform(docs)
# 3. 查看主题
topic_model.get_topic_info().head(3)
# 输出: Topic 0: [game, team, ball...], Topic 1: [key, chip, encryption...]
对比:
- Prompt: 适合处理单条数据的精细理解。
- BERTopic: 适合对百万级数据进行宏观鸟瞰。
本章小结
本章是应用开发的起点,我们完成了从指令设计 到系统构建的跨越:
- Prompt Engineering :不只是写句子,而是结构化编程(Role, Context, Constraints)。
- In-Context Learning :利用Few-Shot 和动态示例检索,无需训练即可通过图灵测试。
- CoT:通过显式思维链,解锁了模型的复杂推理能力。
- 实战落地 :通过SetFit等工具,我们将 Prompt 的思想延伸到了轻量级训练领域。
核心心法:
"不要试图让模型'猜'你的意图,要像写代码一样,给它清晰、明确、结构化的指令。"
思考练习
- Prompt 逆向工程:找一个 ChatGPT 的优秀回答,尝试反推它的 System Prompt 是怎么写的?
- CoT 陷阱:在什么情况下,使用 CoT 反而会降低模型的效果?(提示:简单任务或知识检索任务)
- Few-Shot 鲁棒性:如果示例中的标签是错误的(Label Noise),LLM 还能正确分类吗?这说明了什么?
参考资料
- OpenAI Prompt Engineering Guide : platform.openai.com/docs/guides/prompt-engineering
- Chain-of-Thought Paper: "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models" (Wei et al., 2022)
- SetFit : https://github.com/huggingface/setfit
- Lilian Weng Blog: "Prompt Engineering" (lilianweng.github.io)