Prompt Engineering高级技巧:思维链与少样本学习实战
从基础到进阶,掌握让LLM输出质变的核心技术
前言
很多人用ChatGPT/Claude,停留在"写个提示词让它回答问题"的层面。但真正的Prompt Engineering是一门系统性的技术——如何让LLM推理更准确?如何控制输出格式?如何减少幻觉?
本文将深入讲解Prompt Engineering的核心技巧,包括Chain-of-Thought、Few-Shot Learning、Self-Consistency、Tree-of-Thought等高级方法,配合LangChain实战代码,帮你从"会用"进阶到"精通"。
一、Prompt Engineering的核心原理
1.1 LLM如何理解提示词
LLM本质上是一个条件概率模型:
P(next_token | context) = softmax(W · h + b)当你输入一个提示词,LLM会:
- Tokenize:将文本切分为token序列
- Encode:通过Transformer编码,每个token得到一个向量表示
- Attend:注意力机制计算token之间的关系
- Decode:逐个生成下一个token,直到遇到终止符
# 查看token化结果importtiktoken enc=tiktoken.encoding_for_model("gpt-4")tokens=enc.encode("Hello, how are you?")print(tokens)# [9906, 11, 1268, 527, 499, 30]print([enc.decode([t])fortintokens])# ['Hello', ',', ' how', ' are', ' you', '?']1.2 提示词的四个要素
一个高质量的提示词通常包含四个要素:
prompt=""" [角色] 你是一位资深的Python后端工程师,擅长设计高性能API。 [指令] 请为以下需求设计一个RESTful API,包含完整的路由定义和错误处理。 [上下文] 我们正在开发一个电商平台,需要用户管理和商品管理功能。 [格式] 输出为Python FastAPI代码,包含类型注解和docstring。 [约束] - 不使用任何ORM - 错误处理使用标准HTTP状态码 - 每个接口添加请求/响应示例 """1.3 提示词工程的三个层次
Level 1: 直接提问 "帮我写一个排序算法" Level 2: 结构化提示 "请用Python实现快速排序,要求: 1. 支持自定义比较函数 2. 原地排序,空间复杂度O(log n) 3. 包含完整的类型注解和单元测试" Level 3: 思维链引导 "我需要一个高效的排序算法。让我分析一下需求: - 数据规模:100万条记录 - 数据特征:大部分已排序,少量乱序 - 内存限制:不能使用额外O(n)空间 基于以上分析,快速排序可能不是最优选择,因为: 1. 已排序数据会导致快排退化为O(n²) 2. 需要选择合适的pivot策略 请给出你的分析和实现方案。"二、Chain-of-Thought(思维链)
2.1 基本概念
Chain-of-Thought(CoT)是一种让LLM展示推理过程的技术。不是直接给答案,而是让模型"一步一步思考"。
Zero-shot CoT:在提示词中加入"Let’s think step by step"
# 普通提示prompt=""" Q: 一个商店有23个苹果,卖掉了一些后剩下8个。又进了6个。现在有多少个苹果? A: """# LLM可能直接输出:15# Zero-shot CoTprompt=""" Q: 一个商店有23个苹果,卖掉了一些后剩下8个。又进了6个。现在有多少个苹果? A: Let's think step by step. """# LLM会输出:# 1. 初始有23个苹果# 2. 卖掉了一些后剩下8个# 3. 又进了6个# 4. 现在有 8 + 6 = 14个苹果2.2 Few-shot CoT
给模型几个带推理过程的示例,让它学会"展示推理过程":
prompt=""" Q: 一个商店有23个苹果,卖掉了一些后剩下8个。又进了6个。现在有多少个苹果? A: 让我一步步分析: 1. 初始苹果数:23个 2. 卖掉后剩余:8个 3. 又进货:+6个 4. 当前总数:8 + 6 = 14个 所以答案是14个苹果。 Q: 小明有15元,买了3支笔每支2元,又买了1本书5元。还剩多少钱? A: 让我一步步分析: 1. 小明初始有:15元 2. 买笔花费:3 × 2 = 6元 3. 买书花费:5元 4. 总花费:6 + 5 = 11元 5. 剩余:15 - 11 = 4元 所以小明还剩4元。 Q: 一个水池有100升水,每小时流出8升,同时流入3升。6小时后有多少水? A: 让我一步步分析: """# LLM会模仿示例的推理格式2.3 Auto-CoT(自动思维链)
手动编写示例很麻烦,Auto-CoT让LLM自动生成推理示例:
fromlangchain.promptsimportPromptTemplatefromlangchain.chainsimportLLMChain# 步骤1:让LLM自动生成推理示例auto_cot_prompt=""" 请为以下问题生成一个详细的推理过程示例: 问题:{question} 要求: 1. 分解为3-5个步骤 2. 每一步都有明确的推理 3. 最后给出明确的答案 """# 步骤2:将生成的示例作为few-shot样本defgenerate_cot_examples(llm,questions):examples=[]forqinquestions:chain=LLMChain(llm=llm,prompt=PromptTemplate(template=auto_cot_prompt,input_variables=["question"]))example=chain.run(question=q)examples.append({"question":q,"reasoning":example})returnexamples2.4 CoT的适用场景
CoT在以下场景效果最好:
| 场景 | 效果 | 原因 |
|---|---|---|
| 数学推理 | ⭐⭐⭐ | 需要多步计算 |
| 逻辑推理 | ⭐⭐⭐ | 需要演绎/归纳 |
| 代码调试 | ⭐⭐ | 需要追踪执行流程 |
| 文本分类 | ⭐ | 通常不需要推理 |
| 创意写作 | ⭐ | 限制了创造性 |
三、Few-Shot Learning高级技巧
3.1 示例选择策略
随机选择 vs 战略选择:
# 错误:随机选择示例importrandom examples=random.sample(all_examples,k=3)# 正确:选择与目标问题相似的示例fromsklearn.feature_extraction.textimportTfidfVectorizerfromsklearn.metrics.pairwiseimportcosine_similaritydefselect_similar_examples(target,candidates,k=3):"""选择与目标问题最相似的k个示例"""vectorizer=TfidfVectorizer()all_texts=[target]+[c["question"]forcincandidates]tfidf=vectorizer.fit_transform(all_texts)similarities=cosine_similarity(tfidf[0:1],tfidf[1:])[0]top_k_indices=similarities.argsort()[-k:][::-1]return[candidates[i]foriintop_k_indices]3.2 示例排序的影响
示例的顺序会影响模型的输出质量:
# 策略1:从简单到复杂examples_sorted=sorted(examples,key=lambdax:x["difficulty"])# 策略2:最相似的放最后(recency bias)examples_reordered=examples[1:]+[examples[0]]# 策略3:多样性排序(避免示例过于相似)defdiverse_sort(examples):"""确保相邻示例不太相似"""result=[examples[0]]remaining=examples[1:]whileremaining:last=result[-1]# 选择与上一个最不相似的least_similar=min(remaining,key=lambdax:similarity(last,x))result.append(least_similar)remaining.remove(least_similar)returnresult3.3 动态示例选择
根据输入动态选择最合适的示例:
fromlangchain.promptsimportFewShotPromptTemplate,PromptTemplatefromlangchain.vectorstoresimportFAISSfromlangchain.embeddingsimportOpenAIEmbeddingsclassDynamicFewShotSelector:def__init__(self,examples):self.examples=examples self.embeddings=OpenAIEmbeddings()self.vectorstore=FAISS.from_texts([e["question"]foreinexamples],self.embeddings,metadatas=examples)defselect(self,query,k=3):"""根据查询动态选择最相关的示例"""results=self.vectorstore.similarity_search