Day 4 详细展开:Prompt 基础 —— 零样本与少样本(Zero-shot & Few-shot)
欢迎来到第四天!从今天开始,我们将进入提示词工程(Prompt Engineering)的核心领域。今天我们聚焦两种最基础的提示策略:零样本(Zero-shot)和少样本(Few-shot)。理解它们并能够灵活运用,是驾驭大模型能力的关键第一步。
一、今日学习目标
- 理解 Zero-shot 和 Few-shot 的概念及区别。
- 通过实验对比:在同一任务下,提供示例(Few-shot)是否比不提供示例(Zero-shot)效果更好、更稳定。
- 学会设计一个“关键词提取器”提示词,并实现 JSON 格式的结构化输出。
- 掌握处理模型输出不稳定问题的基本技巧(如温度调节、输出解析重试)。
- 初步体会到 Prompt 设计对模型行为的影响力。
二、详细实现步骤
步骤 1:理解 Zero-shot 与 Few-shot
Zero-shot(零样本):不给模型任何示例,直接描述任务要求,让模型根据其预训练知识来完成任务。例如:
请从以下文本中提取所有地名,以 JSON 数组返回。
模型必须完全依靠它对“地名”的理解来完成任务。
Few-shot(少样本):在 Prompt 中提供 1~3 个示例(输入-输出对),让模型模仿示例的格式和逻辑来完成新任务。例如:
示例 1:
输入:小明去北京旅游。
输出:[“北京”]示例 2:
输入:上海和广州都是大城市。
输出:[“上海”, “广州”]现在,请提取以下文本中的地名:
输入:我计划下个月去成都和重庆出差。
为什么 Few-shot 通常更有效?因为大语言模型本质上是“模式匹配器”,示例能够更明确地告诉模型你期望的输出格式、风格和抽取粒度,减少模型自由发挥的空间。
步骤 2:搭建关键词提取任务实验
我们将设计一个“关键词提取器”,输入一段句子,输出其中最关键的名词或短语(例如产品名、地名、人名等)。我们将分别用 Zero-shot 和 Few-shot 来测试,并比较它们的输出格式一致性。
新建few_shot_demo.py:
importosimportjsonfromdotenvimportload_dotenvfromopenaiimportOpenAI load_dotenv()client=OpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"),base_url="https://api.deepseek.com")defextract_keywords_zero_shot(text:str)->str:"""零样本:只给任务描述,不给示例"""prompt=f""" 请从以下文本中提取所有重要关键词(包括人名、地名、组织名、产品名等), 要求以 JSON 数组形式返回,不要包含其他多余文字。 文本:{text}"""response=client.chat.completions.create(model="deepseek-chat",messages=[{"role":"user","content":prompt}],temperature=0.2,max_tokens=200)returnresponse.choices[0].message.contentdefextract_keywords_few_shot(text:str)->str:"""少样本:提供两个示例"""prompt=f""" 你是一个关键词提取助手。请从给定文本中提取所有重要关键词(人名、地名、组织名、产品名等), 并以 JSON 数组格式返回。只输出 JSON 数组,不要输出其他任何解释。 示例 1: 输入:苹果公司发布了新款 iPhone。 输出:["苹果公司", "iPhone"] 示例 2: 输入:张三去上海参加人工智能大会。 输出:["张三", "上海", "人工智能大会"] 现在,请提取以下文本中的关键词: 输入:{text}"""response=client.chat.completions.create(model="deepseek-chat",messages=[{"role":"user","content":prompt}],temperature=0.2,max_tokens=200)returnresponse.choices[0].message.content# 测试文本test_text="李华在腾讯公司工作,最近他去了成都出差,并参观了当地的宽窄巷子。"print("========== Zero-shot 输出 ==========")zero_result=extract_keywords_zero_shot(test_text)print(zero_result)print("\n========== Few-shot 输出 ==========")few_result=extract_keywords_few_shot(test_text)print(few_result)# 尝试解析 JSON 看格式是否正确deftry_parse_json(s:str):try:# 模型有时会输出 markdown 代码块,需要去除ifs.startswith("```"):s=s.strip("`").strip()ifs.startswith("json"):s=s[4:]returnjson.loads(s)exceptExceptionase:returnf"解析失败:{e}"print("\nZero-shot 解析结果:",try_parse_json(zero_result))print("Few-shot 解析结果:",try_parse_json(few_result))运行脚本:
python few_shot_demo.py观察与思考:
- Zero-shot 输出的格式可能不太稳定,有时会带上“好的,关键词如下:”之类的废话,或者 JSON 格式不标准(例如使用单引号,或缺少方括号)。
- Few-shot 因为给出了明确的示例,模型通常能严格模仿输出格式,更容易解析。
- 你可以多运行几次 Zero-shot(将
temperature稍微调高到 0.5),会发现输出变化较大;而 Few-shot 的输出相对稳定。
步骤 3:优化输出稳定性 —— 输出解析与重试
在实际项目中,我们不能完全依赖模型的自觉性,需要代码层面增加容错。上面我们写了try_parse_json函数,现在我们可以扩展它,如果解析失败,就重新请求模型,并追加一条错误反馈,要求它严格输出 JSON。
修改few_shot_demo.py,添加带重试的版本:
importtimedefextract_keywords_few_shot_with_retry(text:str,max_retries=2)->list:"""带重试机制的少样本提取,返回解析后的列表"""retries=0whileretries<=max_retries:raw=extract_keywords_few_shot(text)# 清理 markdown 代码块标记cleaned=raw.strip()ifcleaned.startswith("```"):cleaned=cleaned.strip("`")ifcleaned.startswith("json"):cleaned=cleaned[4:]try:result=json.loads(cleaned)ifisinstance(result,list):returnresultexceptException:pass# 解析失败,进入重试retries+=1# 在重试前,可以修改 Prompt 强调 JSON 格式,这里简单 sleep 一下time.sleep(1)# 最终失败则返回空列表return[]result=extract_keywords_few_shot_with_retry(test_text)print("\n带重试的 Few-shot 最终解析结果:",result)注意:这个简单版本只是重新调用相同的 Prompt,如果模型持续输出格式错误,你可能需要动态修改 Prompt,比如追加“上次你输出的格式不对,请只输出 JSON 数组,不要包含任何其他文字”。
步骤 4:探索不同示例数量的影响(选做)
你可以尝试在 Few-shot 中提供 0 个、1 个、3 个示例,分别测试输出质量。通常 1~3 个示例已经足够,过多示例会增加 Token 消耗且收益递减。
三、常见问题与调试
Q1:模型输出中总是包含“好的,这是结果:”之类的多余文字,导致 JSON 解析失败。
→ 解决方法:
- 在 System Prompt 中明确“只输出 JSON 数组,不要有任何解释”。
- 使用更低的 Temperature(如 0.1)。
- 使用 LangChain 等框架的输出解析器(后续会学)。
- 编写一个健壮的提取函数,从文本中定位 JSON 片段(比如用正则找到
[...]部分)。
Q2:Few-shot 示例中的格式必须和真实任务完全一致吗?
→ 不必完全一致,但越相似,模型迁移效果越好。建议示例尽量覆盖不同情况(如多个关键词、特殊符号等)。
Q3:为什么有时候 Zero-shot 的结果也能用?什么时候必须用 Few-shot?
→ 对于模型已经很熟悉的简单任务(如情感分析),Zero-shot 通常足够。但当任务有特定的输出格式、特定的分类体系、或者需要对领域术语进行精确抽取时,Few-shot 能大幅提高准确率。在 Agent 开发中,往往需要 Few-shot 来规范工具调用的参数格式。
Q4:示例是否会被模型“记住”?
→ 模型不会像数据库那样记住示例,示例只在当前请求的上下文中起作用。每次请求都需要重新提供示例(除非你使用了外部记忆模块,那是后续的内容)。
四、今日总结与作业
今天你完成了:
- ✅ 理解了 Zero-shot 和 Few-shot 的概念。
- ✅ 通过代码实验对比了两种策略在关键词提取任务上的效果差异。
- ✅ 学习了如何处理模型输出的 JSON 格式不稳定问题(清洗、重试)。
- ✅ 初步体会了 Prompt 设计的重要性。
今日作业(必做):
- 自己设计一个“情感分类器”任务:输入一段评论,输出
{"sentiment": "positive/negative/neutral"}。分别用 Zero-shot 和 Few-shot 实现,并测试 5 条不同风格的评论,比较输出解析成功率。 - 尝试在 Few-shot 的 Prompt 中加入一个“边界示例”,例如输入“今天天气不错”这种没有明显关键词的句子,看看模型能否正确返回空数组(或你认为合适的输出)。这能帮助模型处理边界情况。
- (思考题)在构建 Agent 时,工具调用的参数往往是 JSON 格式。你打算如何设计 Few-shot 示例来指导模型精确生成工具参数?请简述你的思路。
明日预告:我们将学习思维链(Chain-of-Thought,CoT)技巧,让模型在回答问题前先展示推理过程,从而提升复杂问题的准确率。
有任何问题欢迎随时提问!