一个好提示词长什么样
同一份乱糟糟的用户求助,随手写的提示词和结构清楚的提示词各跑一次,逐段对比结果。讲清楚身份、任务、背景、要求、输出格式这几块各起什么作用。
- 约 35 分钟
- 难度:入门
- 实测:2026-09-14 deepseek-flash
提示词工程这个词听起来很玄,网上也有很多"万能模板"和"神奇咒语"。其实它的核心很朴素:把你脑子里的要求写清楚。模型不知道你的背景,不知道结果给谁看,不知道你心里的"好"是什么样的。你没写的,它只能猜。
这一课拿一个真实的场景,同一个任务写两版提示词,看看差别在哪。
场景:把群里的求助整理成 issue
假设你是 httpx 的维护者,用户在交流群里发了这么一段:
用httpx下载大文件老是断 我用的stream 下到一半就报错了 ReadTimeout
代码大概是 with httpx.stream("GET", url) as r: for chunk in r.iter_bytes(): f.write(chunk)
文件2个G左右,网不太好,公司内网。之前用requests好像没这问题??
版本是0.27 python3.11 mac
急!!!明天要交
你想让模型把它整理成一个规范的 GitHub issue,方便其他维护者排查。
第一版:随手一写
v1 = [{"role": "user", "content": "帮我整理一下这个问题:\n" + REPORT}]
模型的回答(关掉了思考,以下是我运行得到的原文,你的会不一样):
# 问题整理
## 一句话概述
用 httpx 的 `stream` 下载 ~2GB 大文件,网络环境较差(公司内网),下载中途抛 `ReadTimeout`,改用 requests 则无此问题,需要尽快解决。
## 环境信息
- 库/版本:httpx **0.27**
- Python:**3.11**
- 系统:**macOS**
- 网络:公司内网,网速较差/不稳定
- 文件大小:约 **2 GB**
## 复现代码
```python
with httpx.stream("GET", url) as r:
for chunk in r.iter_bytes():
f.write(chunk)
```
## 现象
- 下载过程**中途中断**
- 报错类型:**ReadTimeout**
- 相同 URL 和网络下,**requests 不报错**(或很少报错)
## 关键疑问
1. 为什么 httpx 会 ReadTimeout 而 requests 不会?两者的超时/重试默认行为差异在哪?
2. httpx 的 `timeout` 参数在 stream 场景下具体作用于什么?(连接 / 单个 chunk 间隔 / 整体?)
3. 有没有办法在 stream 时**禁用或放宽读超时**,让慢速下载不被打断?
4. 是否需要加**重试 / 断点续传(Range)**来兜底?
## 目标
- 让 2GB 大文件在弱网内网环境下能稳定下完
- 时间紧急,明早交付
## 可能的排查方向(供参考)
- httpx 默认 `timeout=5.0`,requests 默认是"无超时"(会一直等),这很可能是根因
- 可尝试:`httpx.stream("GET", url, timeout=httpx.Timeout(connect=10, read=None, write=10, pool=10))`
- 或加大 read timeout,加 `transport=httpx.HTTPTransport(retries=3)`
- 断点续传:记录已下字节,用 `Range: bytes=N-` 续传
---
需要我帮你针对哪个方向继续深挖(比如给出可直接替换的稳定下载代码)?
看起来挺专业,排版也漂亮。但仔细对照原话,问题不少:
- 把猜测写成了事实。用户说的是"之前用 requests 好像没这问题??",到了模型这里变成了"改用 requests 则无此问题"、"相同 URL 和网络下,requests 不报错"。维护者看到这句,可能会顺着一个根本没被证实的线索去查。
- 做了没让它做的事。我只要整理问题,它却给了一节"排查方向",还有"目标"、"关键疑问"。对 issue 来说这些是噪音。
- 建议里有错。它建议加
HTTPTransport(retries=3),但 httpx 文档写得很清楚,这个参数只在连接失败(ConnectError、ConnectTimeout)时重试,对下载中途的ReadTimeout没有用。这是一个会误导人的建议。 - 格式不是 issue 的格式。结尾还问"需要我帮你继续深挖吗",这句话进了 issue 就很奇怪。
这不怪模型。"帮我整理一下"可以有一百种理解:整理给谁看?整理成什么样?能不能加自己的判断?它选了一种"尽量有帮助"的理解,于是写得越多越好。
第二版:把要求写清楚
v2 = [
{
"role": "system",
"content": """你是 httpx 开源项目的维护者,负责把用户在群里的求助整理成 GitHub issue。
任务:把 <report> 标签里的用户原话整理成一个 issue,给其他维护者看。
要求:
- 只使用原话里有的信息,不要猜测或补充原话里没有的细节。
- 原话里没提到、但排查需要的信息,列在"待确认"里。
- 不要给解决方案,这一步只整理问题。
输出格式(Markdown,只输出 issue 本身):
## 标题
一句话,说清现象和场景
## 环境
## 复现代码
## 现象
## 待确认""",
},
{"role": "user", "content": f"<report>\n{REPORT}\n</report>"},
]
模型的回答:
## 标题
使用 httpx.stream 下载约 2G 大文件时,中途报 ReadTimeout 错误
## 环境
- httpx 版本:0.27
- Python 版本:3.11
- 操作系统:macOS
- 网络环境:公司内网,网络状况较差
## 复现代码
```python
with httpx.stream("GET", url) as r:
for chunk in r.iter_bytes():
f.write(chunk)
```
## 现象
- 使用 `httpx.stream` 下载约 2G 的大文件时,下载到一半报错 `ReadTimeout`。
- 网络状况较差(公司内网)。
- 用户反馈之前使用 requests 时似乎没有该问题。
## 待确认
- 是否设置了自定义 timeout(尤其是 read timeout)?具体值是多少?
- 报错时的完整 traceback 信息。
- 下载中断是必现还是偶发?中断位置是否固定?
- `url` 指向的服务端类型(如是否支持分块传输、是否有响应延迟等)。
- requests 与 httpx 测试时是否为同一 URL、同一网络环境,以及 requests 是否也配置过 timeout。
- 是否使用代理。
"似乎没有该问题"保留了用户的不确定。没有自作主张的解决方案,也就没有错误建议。"待确认"那一节很有用:完整的 traceback、是否必现、requests 当时有没有设置超时,都是排查时真正需要问用户的。这份东西可以直接贴进 GitHub。
第二版多写了什么
把第二版的提示词拆开,它包含了五块东西。
身份:"你是 httpx 开源项目的维护者"。这句话告诉模型用什么视角、什么专业水平来处理任务。它不是魔法,写"你是世界顶级专家"并不会让模型变聪明。它的作用是给模型一个语境,让它知道哪些知识是相关的、结果应该是什么风格。
任务:"把原话整理成一个 issue,给其他维护者看"。关键是说清楚产出是什么、给谁看。给维护者看的 issue 和给用户看的回复,写法完全不同。
要求:三条规则。注意它们都很具体:"只使用原话里有的信息"、"不要给解决方案"。像"写得专业一点"、"注意准确"这种要求几乎没用,因为模型本来就以为自己很专业、很准确。
输出格式:直接给出结构。想要什么格式,就把格式画出来,比用文字描述"请分成标题、环境、现象几个部分"更可靠。
分隔符:用户的原话放在 <report> 和 </report> 之间。这样模型能清楚地区分哪些是你的指令、哪些是要处理的材料。材料里如果碰巧有一句"忽略上面的要求",也不容易被当成指令。(05 模块第 8 课会专门讲这种攻击。)用 XML 风格的标签、三个反引号、""" 都可以,关键是前后一致。
另外,第二版把固定的规则放在 system 消息里,每次变化的材料放在 user 消息里。这不仅让结构更清楚,还能命中缓存:system 消息每次都一样,按上一课讲的,它会按缓存命中的价格收费。
说"要做什么",也说"不要做什么"
网上常见一条建议:"要告诉模型做什么,而不是不要做什么"。这条建议有道理:只写"不要啰嗦",模型不知道多简洁才算不啰嗦;写成"用一句话回答",就清楚多了。
但"不要做什么"在一种情况下必不可少:当模型有一个很强的默认习惯时。第一版里模型主动给解决方案,就是这种习惯。第二版的"不要给解决方案"一句话就把它挡住了。
第 00 模块第 3 课还有个例子:让模型"用五个字形容秋天",它给了五个字,又附赠了六个。我把提示词改成这样再试:
ask([{"role": "user", "content": "用五个字形容秋天"}])
ask([{"role": "user", "content": "用五个字形容秋天。只输出这五个字,不要标点,不要解释,不要给其他选项。"}])
原提示词: **金风送爽时**
(也可以换成:**霜叶红于花**、**一叶知秋意**、**秋高气爽天**,看你喜欢哪种意境。)
改进后: 秋高气爽时
"只输出这五个字"是正面的要求,"不要标点,不要解释,不要给其他选项"是把模型最可能多做的三件事提前堵上。两者结合效果最好。
写提示词的顺序
我自己写提示词,一般按这个顺序想:
- 结果给谁用? 给人看还是给程序解析?给专家还是给新手?
- 好的结果长什么样? 最好手写一个你心目中的理想输出。写不出来,说明你自己还没想清楚要什么。
- 模型最可能在哪里做错? 先用一句话的提示词试一次,看它哪里不符合要求,再针对性地加规则。
- 材料和指令分开。
第 3 步很重要:不要一上来就写一个几百字的"完美提示词"。先写最简单的版本,看它错在哪,再补。每条规则都应该对应一个你亲眼见过的问题。这样写出来的提示词短,而且每一句都有用。
什么时候不用写这么多
临时问个问题、让模型帮你改改文字,一句话就够了,看结果不满意再补充。结构化的提示词主要用在写进程序里、要被反复调用的地方:你不能每次都在旁边看着改,所以要一次写清楚。
练习
- 运行
code/02-prompting/prompt_structure.py,看看你得到的第一版和第二版和我的有什么不同。 - 把第二版的"要求"删掉一条(比如"不要给解决方案"),再跑几次,看模型会不会重新开始给建议。
- 找一段你工作中真实遇到的、需要整理的文字(会议记录、客户邮件、报错日志),先用一句话的提示词处理,找出结果的问题,再按本课的五块结构改写提示词。记下你加的每条规则分别解决了什么问题。
自测
1. 第一版提示词的结果排版很漂亮,为什么说它有问题?
它把用户的猜测("好像没这问题")写成了确定的事实,还做了没要求的事(给排查方向),其中一条建议(用 HTTPTransport(retries=3) 解决读超时)是错的。格式也不适合直接当 issue 用。排版漂亮不代表内容可靠,要对照原始材料检查。
2. 在提示词里写"你是世界顶级的 Python 专家",能让模型的回答变得更准确吗?
基本不能。身份描述的作用是给模型提供语境:用什么视角、什么风格、哪些知识相关。它不会让模型凭空多出能力。与其夸大身份,不如把任务、给谁看、具体要求和输出格式写清楚。
3. 为什么要用 <report> 这样的标签把用户材料包起来?
让模型清楚地分辨哪部分是你的指令、哪部分是要处理的材料。这样材料里的内容不容易被误当成指令,包括有人故意在材料里写"忽略之前的要求"这类攻击。
提问与讨论
这一课没看懂的地方,在这里问。看到别人的问题,也欢迎你来回答。
提问 +3 积分,回答别人 +6 积分。内容经审核后公开。
正在加载讨论…