大模型应用技术-Prompt工程实践 --- 学习笔记
来源:华为云 KooLabs 实验手册《大模型应用技术-prompt工程实践》 实验模型:DeepSeek-v4-flash (华为云 MAAS 推理服务) 实验时长:2小时 | 难度:初级 | 评分:5.0 实验人次:2.1k
一、实验概览
实验目标
- 掌握 DeepSeek 大模型 API 的调用方法
- 掌握 DeepSeek 大模型各个参数的含义与用法
- 掌握 Prompt 工程的技巧
实验背景
- 基于 MAAS 提供的推理服务为调用目标
- 在沙箱环境中用 PyCharm 工具调用 DeepSeek 模型 API
- 使用 Prompt 工程调优技巧实现要求的模型输出
实验费用
- 使用华为云 MAAS 服务,预计费用 1 元
- 需个人华为云账号(已实名认证、余额充足)
- 云资源费用自行承担
二、实验架构总览
scss
┌─────────────────────────────────────────────────────────────┐
│ 实验整体架构 │
│ │
│ ┌──────────┐ API调用 ┌──────────────────┐ │
│ │ PyCharm │ ────────────→ │ DeepSeek-v4-flash │ │
│ │ Python脚本│ │ (MAAS推理服务) │ │
│ └──────────┘ └──────────────────┘ │
│ │ │ │
│ │ payload参数 │ 返回 choices[0] │
│ │ - model │ message.content │
│ │ - messages │ │
│ │ - max_tokens │ │
│ │ - temperature │ │
│ │ - top_p │ │
│ │ - top_k │ │
│ │ - stream │ │
│ └──────────────────────────────┘ │
│ │
│ Prompt工程技巧层: │
│ 上下文问答 → 样本提示 → CoT → 链式提示 → RAG → ReAct │
└─────────────────────────────────────────────────────────────┘
三、第一部分:大模型 API 调用与调参
核心代码模板(TextGenerationClient 封装类)
python
import requests
import json
class TextGenerationClient:
def __init__(self, api_url, api_key):
self.api_url = api_url
self.api_key = api_key
def generate_text(self, payload):
headers = {
"Content-Type": "application/json",
'Authorization': f'Bearer {self.api_key}'
}
response = requests.post(self.api_url, headers=headers, data=json.dumps(payload))
if response.status_code == 200:
return response.json()['choices'][0]['message']['content']
else:
raise Exception(f"Error: {response.status_code} - {response.text}")
标准请求 payload 结构
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "系统提示词"},
{"role": "user", "content": "用户问题"}
],
"max_tokens": 150, # 最大生成 token 数
"top_p": 1, # 核采样参数
"temperature": 0, # 温度参数
"top_k": -1, # Top-k 采样(-1=不限制)
"ignore_eos": "false", # 是否忽略结束符
"stream": False # 是否流式输出
}
步骤5:调试温度(Temperature)采样参数
实验设计
- 任务:
介绍下大语言模型 - 对比不同 temperature 值下两次输出的差异
| 参数设置 | 实验观察 | 结论 |
|---|---|---|
temperature=0(贪婪采样) |
两次输出完全相同 | 总是选概率最高的 token,确定性最高 |
temperature=1(高随机) |
两次输出不同 | 高随机性,低概率 token 也有机会被选中 |
temperature=0.1(低随机) |
初期完全一致,随采样增加可能出现差异 | 几乎确定,但随机性本身仍存在 |
关键思考
如何完全杜绝小概率 token 被采样? 答:设置
temperature=0(贪婪采样/greedy sampling),模型总是选取词汇表中概率最高的单词,完全消除随机性。
温度参数总结
| Temperature 值 | 采样行为 | 适用场景 |
|---|---|---|
| 0 | 贪婪采样,每次选最高概率 token | 确定性任务、代码生成、事实问答 |
| 0.1~0.3 | 低随机,几乎确定 | 文档检索、科学计算、需要稳定输出 |
| 0.5~0.7 | 中等随机 | 日常对话、创意写作 |
| 1.0+ | 高随机,低概率 token 也有机会 | 头脑风暴、创意生成、多样化输出 |
步骤6:调试 Top 采样参数(Top_p)
实验设计
- 任务:
介绍下大语言模型 - 对比不同 top_p 值下两次输出的差异
| 参数设置 | 实验观察 | 结论 |
|---|---|---|
top_p=1 |
考虑 100% 概率的 token | 相当于不使用 Top-P 采样 |
top_p=0.1 |
几乎只采样最可能的 token | 候选集极小,但不一定是最合适的 token |
关键洞察
Top_p 在 0.1 的情况下,几乎可以认为每次采样仅采样最有可能的 token,然而却并不一定是最合适的 token。
Top_p vs Temperature 对比
| 参数 | 控制方式 | 特点 |
|---|---|---|
| Temperature | 整体调整概率分布的"尖锐度" | 高温度→分布更平坦,低温度→分布更尖锐 |
| Top_p | 动态选择候选集(累积概率达到 p) | 概率集中时候选少,分散时候选多 |
实际调参建议:通常不同时设置 top_p 和 top_k,二选一即可;temperature 和 top_p 可以配合使用。
四、第二部分:Prompt 工程技巧(核心实战)
实验场景设定
- 目标文章:由模型生成一篇包含三个人物的短篇小说(500字以内)
- 核心问题 :
文中的四个主角是什么关系?(这是一个陷阱问题------文章中实际只有三个人物) - 测试目标:不同 Prompt 技巧如何帮助模型识别并正确处理这个陷阱
步骤1:构建目标文章
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是一个短篇小说创作者,写一篇短篇小说,500字以内"},
{"role": "user", "content": "3个人物,2个情节"}
],
"max_tokens": 800,
"top_p": 1,
"temperature": 1,
}
注意:模型输出不稳定,可通过调整采样参数控制输出。实验为保证后续步骤有稳定参考,提供了预设文章内容。
预设文章内容(奶奶与孙女的家庭故事):
arduino
奶奶把最后一件毛衣挂进衣柜,那件驼色的,触感柔软得像傍晚的阳光。
她回头看看窗外,院子里那棵老槐树已经落光了叶子。
孙女佳怡的电话就在这时打来了。"奶奶,降温了!你记得把厚被子拿出来,
床头柜第二个抽屉有新买的暖宝宝。"
楼下传来关门声,是儿子阿平回来了。他提着一个纸袋,里面装着新烤的面包。
"妈,明天早餐吃这个,您不用早起了。"
厨房里,他无意间打开冰箱,愣了一下------那个熟悉的保鲜盒又出现了,
里面整齐码着他最爱吃的萝卜丝饼。旁边贴着一张便签:"热三分钟,配豆浆。"
佳怡在电话那头提高了声音:"爸爸肯定又忘记喝热水!奶奶您监督他,
他喉咙不好还总喝凉的。"阿平就在旁边不好意思地笑,给母亲倒了杯温水。
傍晚,祖孙三代坐在客厅。电视开着,但谁也没认真看。佳怡通过视频展示
她新布置的小家:"这里要放一张摇椅,等奶奶来住。"阿平剥好橘子,
一瓣给母亲,一瓣放进自己嘴里,剩下的大半个自然地递到镜头前,
仿佛女儿就在身边。
夜深了,阿平帮母亲掖好被角,自己回到房间。床头柜上除了保温杯,
不知何时多了一小瓶蜂蜜。
他点开家庭群,发了一条消息:"暖气很足,都暖和。晚安。"
很快,手机轻轻震动------来自两个不同方向的回复,拼成同一个夜晚的温度:
"奶奶/爸爸/女儿,晚安。"
文章人物关系 :奶奶(母亲)、阿平(儿子)、佳怡(孙女)------共3人,非4人。
步骤2:基于上下文的问答技巧
实验目的
比较有上下文 和无上下文两种场景对大模型推理的影响。
有上下文(正确做法)
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"你是一个文本阅读器,读取文章的内容并按照用户的要求输出对于文章的回答,以下是文章{content}"},
{"role": "user", "content": "根据文章回答段誉干了什么"}
],
"max_tokens": 200,
"top_p": 1,
"temperature": 0.3,
}
→ 模型基于文章内容回答,输出与文章内容一致。
无上下文(错误示范)
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "user", "content": "根据文章回答段誉干了什么"}
],
"max_tokens": 200,
"top_p": 1,
"temperature": 0.3,
}
→ 模型基于自身知识储备回答,可能产生幻觉(段誉是金庸小说人物,与本文无关)。
陷阱测试
python
# 问题:文章中只有3个人物,但问题问"四个主角"
{"role": "user", "content": "根据文章回答:文中的四个主角是什么关系"}
→ 模型对提问的错误并没有指出,直接回答了"四个主角"的关系。
关键洞察 :模型会盲目回答问题,即使问题本身有错误。在实际场景(如客服问答)需要识别提问并回答。
步骤3:样本提示技巧(Few-shot)
实验设计
在 system prompt 中提供一个错误示例,教模型识别"人物数量不符"的陷阱:
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"""
指令:你是一个文本阅读器,读取文章的内容并按照用户的要求输出对于文章的回答,
上下文:"文章:a,b,c,d,e是五个人物,他们之间是亲属关系;
提问:文章的6个人物,他们是什么关系;
回答:文章中只有五个人物,他们是亲属关系"
以下是文章{content}"""},
{"role": "user", "content": "根据文章回答:文中的四个主角是什么关系"}
],
"max_tokens": 200,
"top_p": 1,
"temperature": 1,
}
实验结果
样本提示的方法并不能很好地解决这个问题,因为我们没有办法给出一个很好的例子。
样本提示的适用场景(实验给出的答案)
样本提示在什么样的问题中能具备明显的效果?
答案 :格式输出、固定话术、简单分类、情绪识别、标准化打分; 任务模式固定,只需要模仿示例即可完成,这时样本提示收益最高。
关键洞察
当陷阱类型不确定、场景多变,单一示例适配性很差,样本提示很难覆盖全部异常提问,改善效果有限。
步骤4:思维链(CoT)提示
实验设计
使用零样本 CoT(不给示例,只加"逐步思考"指令):
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"""
指令:你是一个文本阅读器,读取文章的内容并严格按照用户的要求输出对于文章的回答,
你需要逐步思考用户提出问题并逐一回答
以下是文章{content}"""},
{"role": "user", "content": "根据文章回答:文中的四个主角是什么关系"}
],
"max_tokens": 300,
"top_p": 1,
"temperature": 1,
}
实验结果
- 模型在人物关系推理时进行了更多的思考(逐步拆解)
- 但并没有识别出提问的陷阱
关键洞察
零样本 CoT 能增强推理深度,但不能自动纠正问题本身的错误。CoT 让模型"想得更深",但不让模型"质疑问题"。
步骤5:链式提示(Prompt Chaining)
实验设计
四层子任务拆解,任务层层递进、环环相扣:
makefile
子任务1:模型提取信息(文章中有几个人物?什么关系?)
↓
子任务2:匹配用户问题,找出提问的错误
↓
子任务3:就用户的题问进行回答
↓
子任务4:根据错误信息,整合最终回答
完整代码实现
子任务1 - 提取信息:
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"指令:你是一个文本阅读器,读取文章的内容并按照用户的要求输出对于文章的回答,以下是文章{content}"},
{"role": "user", "content": "根据文章回答:文中有几个人物,他们是什么关系"}
],
"max_tokens": 200,
"top_p": 1,
"temperature": 1,
}
info = generated_text # 提取的信息
子任务2 - 找出提问错误:
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"指令:根据提问对比已知的信息,找出提问的错误,并输出错误的点。以下是已知的信息{info}"},
{"role": "user", "content": "提问的问题是:'文中的四个主角是什么关系'"}
],
"max_tokens": 200,
"top_p": 1,
"temperature": 0.3,
}
error = generated_text # 提问的错误
子任务3 - 直接回答问题:
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"指令:你是一个文本阅读器,读取文章的内容并按照用户的要求输出对于文章的回答,以下是文章{content}"},
{"role": "user", "content": "根据文章回答:文中的四个主角是什么关系"}
],
"max_tokens": 200,
"top_p": 1,
"temperature": 1,
}
answer = generated_text
子任务4 - 整合最终回答:
python
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"""
指令:根据错误信息,并修改错误的回答,并按照输出格式化输出正确的回复
上下文:以下是错误信息{error},
以下是需要修改的错误的回答{answer}
输出格式化:问题:
正确的答案应该是:"""},
{"role": "user", "content": ""}
],
"max_tokens": 200,
"top_p": 1,
"temperature": 1,
}
链式提示的本质
把一个复杂大任务切分为一串前后依赖、顺序执行的子问题,上一步的输出直接作为下一步的输入,强制模型分步思考,避免直接跳步给出粗略答案。
链式提示的优势
- 降低大模型的推理难度
- 有效减少幻觉
- 提升长链条复杂任务的完成质量
步骤6:检索增强生成(RAG)
RAG 核心概念
RAG 对 LLM 的作用,就像开卷考试对学生一样:
- 开卷考试中,学生可以带参考资料进场,查找解答问题所需信息
- 核心在于考察推理能力,而非对具体信息的记忆能力
两种知识类型:
| 类型 | 存储方式 | 特点 |
|---|---|---|
| 参数化知识(Parametric) | 模型权重中(训练时学习) | 隐式、难更新、可能过时 |
| 非参数化知识(Non-parametric) | 外部知识源(向量数据库) | 显式、可更新、可溯源 |
RAG 三步工作流程
scss
检索(Retrieval) → 增强(Enhancement) → 生成(Generation)
↑ ↑ ↑
从外部知识源 检索内容拼入 LLM 基于增强
获取相关信息 prompt 模板 提示生成答案
模拟实验流程
第一步:文档切块(模拟向量数据库)
python
js_tmp = {
"段落1": "", "段落2": "", "段落3": "", "段落4": "", "段落5": ""
}
# 模型将文章分割为5段
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"""
指令:分割输入文本,分割成5段,并按照输出格式化输出正确的输出
上下文:以下是输入的文本:{content}
输出格式化:按照格式输出:{js_tmp}"""},
{"role": "user", "content": ""}
],
"max_tokens": 800,
"top_p": 1,
"temperature": 1,
}
data = generated_text # 分割后的数据
第二步:检索(根据问题找相关段落)
python
Q = "阿平是谁?"
k = "2" # 返回最相关的2个段落
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"""
指令:检索输入的提问,从数据中返回最相关的{k}个段落
上下文:以下是输入的提问:{Q}
以下是数据:{data}
输出格式化:按照json格式输出,按照ID分割"""},
{"role": "user", "content": ""}
],
"max_tokens": 800,
"top_p": 1,
"temperature": 1,
}
response = generated_text # 检索结果
第三步:增强生成(基于检索内容回答)
python
Q = "阿平是谁?"
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"""
指令:根据已知信息回答问题,如果已知信息无法回答,输出"我不知道"
上下文:以下是已知信息:{response}
输出格式化:"""},
{"role": "user", "content": f"{Q}"}
],
"max_tokens": 800,
"top_p": 1,
"temperature": 1,
}
RAG 关键特性
找不到信息则固定回复 "我不知道" ------ 防止模型编造答案。
实验验证
| 问题 | 预期行为 |
|---|---|
阿平是谁? |
从检索到的段落中提取信息回答 |
段誉是谁? |
返回"我不知道"(文章中无此人物) |
步骤7:提示词框架 - ReAct
ReAct 核心概念
- 灵感:"行为(Acting)"和"推理(Reasoning)"的协同作用
- 机制 :提示 LLM 为任务生成口头推理轨迹(thought)和操作(act/工具调用)
- 本质 :思考 → 调用工具 → 观察结果 → 再思考 → ... 的交替循环
实验实现
第一步:定义搜索工具
python
def find_paragraphs_by_name(name):
paragraphs = []
for entry in data1: # data1 是从段落中提取的命名实体数据
if entry.get('名字') == name:
paragraphs.append(entry.get('段落'))
if paragraphs:
return paragraphs
else:
print("人物不存在")
return None
第二步:ReAct 处理流程
python
js_tmp2 = '''{"thought": "", "tool_names": "", "search_name": ""}'''
def react_process(user_input):
# 第1轮:模型决定用什么工具
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"""
指令:请充分理解用户的问题,选择合适的工具,并帮助用户一步步解决问题。
工具列表:"find_tool.json"是一个根据人名从数据库中搜索这个人相关事件的工具。
json格式约束:JSON字符串value中需要引用文字时只能使用全角引号" ",
禁止直接使用未转义的半角双引号"。
输出格式化:{js_tmp2}"""},
{"role": "user", "content": f"用户的问题:{user_input}"}
],
"max_tokens": 200,
"top_p": 1,
"temperature": 0.5,
}
generated_text = client.generate_text(payload)
print(f"模型第一次返回:\n {generated_text} \n")
# 解析模型输出,提取工具名和搜索参数
tool_name = extract_and_parse_json(generated_text)["tool_names"]
search_name = extract_and_parse_json(generated_text)["search_name"]
# 第2轮:如果调用了工具,执行工具并基于结果生成最终回答
if tool_name != '':
result = find_paragraphs_by_name(search_name)
print("思考" + extract_and_parse_json(generated_text)["thought"])
print(f"行动(call_find_paragraphs_by_name):{result}")
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": f"""
指令:概述输入的人名和这个人的相关事件。
上下文:人名:{search_name},相关事件:{result}
输出格式化:"""},
{"role": "user", "content": ""}
],
"max_tokens": 800,
"top_p": 1,
"temperature": 1,
}
generated_text = client.generate_text(payload)
print(f"模型第二次返回:{generated_text}")
else:
return "模型无法回答。"
# 调用示例
react_process('告诉我阿平这个人的相关事件')
ReAct 执行流程示意
makefile
用户问题: "告诉我阿平这个人的相关事件"
↓
第1轮模型输出:
thought: "这是一个关于人物信息查询的问题,需要使用搜索工具"
tool_names: "find_paragraphs_by_name"
search_name: "阿平"
↓
执行工具: find_paragraphs_by_name("阿平")
→ 返回: ["楼下传来关门声,是儿子阿平回来了...", "阿平剥好橘子..."]
↓
第2轮模型输出:
response: "阿平是文中奶奶的儿子,佳怡的父亲..."
五、六种 Prompt 工程技巧对比总结
| 技巧 | 核心机制 | 本实验中的表现 | 适用场景 |
|---|---|---|---|
| 上下文问答 | 将文章内容作为 system prompt 注入 | 有上下文→准确;无上下文→幻觉 | 所有需要基于给定内容回答的场景 |
| 样本提示(Few-shot) | 给范例让模型模仿 | 陷阱类型不确定时效果差 | 格式输出、固定话术、简单分类、标准化打分 |
| 思维链(CoT) | "逐步思考"拆解推理过程 | 推理更深,但不能自动纠正问题错误 | 复杂推理、数学题、逻辑判断 |
| 链式提示 | 拆子任务串多步,上一步输出作下一步输入 | 四层拆解有效识别陷阱 | 长链条复杂任务、需要分步验证的场景 |
| RAG | 外挂知识库检索,找不到就说"不知道" | 能有效区分"已知"和"未知" | 知识密集型、需要溯源、知识频繁更新的场景 |
| ReAct | 推理+工具调用交替循环 | 模型能自主选择工具并执行 | 需要接入外部工具/API的场景 |
六、思考题
问题:什么样的任务适合较低的温度采样参数数值?
答案:科学计算、文档检索等
解析 :低温度(0
0.3)意味着输出更确定、更稳定,适合需要精确、可复现结果的任务。高温度(0.71.0+)适合创意生成、头脑风暴等需要多样性的场景。
七、实验小结
本实验以基于 MAAS 服务的 deepseek-v4-flash 模型为调用目标,使用 Prompt 工程调优技巧实现要求的模型输出。
核心收获:
- API 调用:掌握 DeepSeek 模型的标准调用方式和参数含义
- 采样调参:理解 temperature 和 top_p 对输出随机性的影响
- Prompt 工程:从基础上下文问答到高级 ReAct,逐层递进解决复杂问题
- 实战认知:没有一种技巧是万能的------需要根据任务特点选择合适的组合
八、与前四章的知识串联
scss
第01章 第02章 第03章 第04章 第05章(本实验)
能力和局限 盘古套件 提示词工程 提示词实践 Prompt工程实战
│ │ │ │ │
├─ 采样参数 ├─ SFT(改模型) ├─ Prompt组成 ├─ 1基础2结构 ├─ API调用实操
│ Temperature │ 成本最高 │ 指令/上下文/输入 │ N扩展 │ TextGenerationClient
│ Top_p │ │ 格式化输出 │ CO-STAR │ payload参数
│ Top_k │ ─────────────────│ │ CRISPE │ temperature调试
│ penalty │ 三种调整方式: │ ─────────────────│ │ top_p调试
│ │ Prompt<RAG<SFT │ 5大技巧: │ ─────────────────│
├─ 指令微调LLM │ (成本↑) │ 少样本/CoT/ │ 提示词模板 │ 六种技巧实战
│ instruction- │ │ 链式/RAG/ReAct │ 可视化IDE │ 上下文问答
│ input-output │ ─────────────────│ │ 模板库 │ 样本提示
│ │ 盘古微调模块 │ 9.11vs9.8案例 │ │ CoT
│ │ │ │ │ 链式提示
│ ─────────────────│ │ │ │ RAG
│ System+User │ │ │ │ ReAct
│ Prompt │ │ │ │
│ │ │ │ │ ← 全部落地
│ │ │ │ │
│ │ │ │ │ 理论→代码
五章学习路径总结:
- 第01章 打基础:LLM 推理原理、采样参数、为什么需要指令微调
- 第02章 看方案:三种调整模型输出方式,盘古套件能力
- 第03章 学技巧:五大 Prompt 工程技巧(理论)
- 第04章 练实战:Prompt 撰写策略 + 盘古平台工具
- 第05章(本实验) :动手实操------DeepSeek API 调用 + 六种技巧代码实现
九、关键代码片段速查
标准 API 调用模板
python
import requests, json
class TextGenerationClient:
def __init__(self, api_url, api_key):
self.api_url, self.api_key = api_url, api_key
def generate_text(self, payload):
headers = {"Content-Type": "application/json",
'Authorization': f'Bearer {self.api_key}'}
resp = requests.post(self.api_url, headers=headers, data=json.dumps(payload))
return resp.json()['choices'][0]['message']['content']
标准 payload 模板
python
payload = {
"model": "deepseek-v4-flash",
"messages": [{"role": "system", "content": "..."},
{"role": "user", "content": "..."}],
"max_tokens": 150, "top_p": 1, "temperature": 0, "stream": False
}
JSON 解析辅助函数(处理模型输出)
python
import re, json
def extract_and_parse_json(data):
json_pattern = r'```json\s*([\s\S]*?)\s*```'
json_match = re.search(json_pattern, data, re.S)
if json_match:
json_str = json_match.group(1).strip()
else:
brace_start, brace_end = data.find("{"), data.rfind("}")
json_str = data[brace_start: brace_end + 1].strip()
if json_str.startswith('"') and json_str.endswith('"'):
json_str = json_str[1:-1]
json_str = json_str.replace("'", '"')
return json.loads(json_str)