1. 提示工程不是玄学,是一门可以系统化的手艺
两个程序员同时用同一款大模型写代码,一个拿到手的是能直接跑的脚本,另一个收获的是一堆"正确的废话"。问题不在模型能力,而在你怎么提问。我见过太多人把提示工程理解成"把话说清楚",实际上它是在和一种概率系统打交道,你需要理解它怎么读你的话、怎么猜你的意图、在哪一步容易跑偏。
这个职业的门槛很低,低到任何人敲几行字就能启动;但天花板很高,高到同样的需求,有的人一轮搞定,有的人调十轮还在原地打转。核心差距在于:你有没有把"写提示词"这件事,从直觉行为升级成结构化方法。
提示工程(Prompt Engineering)严格来说不是一种编程语言,也不是某种框架,它是你对模型输出行为的一种控制手段。你需要通过自然语言去约束一个概率模型的输出空间,让它在最大概率路径上走你希望它走的那条路。这个过程可以拆解、可以调试、可以复用,并且可以被系统性评估。
如果你是个刚接触代码没多久的小白,或者已经写了一阵子但还停留在"模型说什么我就用什么"的状态,这篇文章就是为你准备的。我会从最底层的 token 机制开始讲,逐步拆解指令结构、进阶策略、调试方法,最后给出一套能直接套用的实战模板。整个过程不装深沉、不铺垫概念,按我实际使用中的节奏来。
先打破一个幻觉:提示工程不是靠灵感和玄学,它背后有明确的原理。你写的每一个词、每一段结构、每一条示例,都在改变模型对"你想要什么"的分布估计。理解这个逻辑之后,你会发现自己能预测模型的"失误",而不是每次都被它气得重新开一轮对话。
2. 从 Token 到上下文:理解模型为什么要"咬文嚼字"
2.1 模型不是在读句子,是在算概率
你要想彻底吃透提示工程,第一站必须落在 token 上。大模型处理文本的最小单位不是字,也不是词,而是 token。Token 是模型把文本切分之后的基本单元,一个 token 可能是半个词、一个词、甚至一个标点符号。
比如"我对提示工程的理解"这句话,模型可能会切分成类似"我对""提示""工程""的""理解"这样的 token 序列。不同模型的分词规则不一样,但共同点是:模型看到的是 token 序列,不是人类的语法树。这意味着,它在生成下一个 token 时,本质上是在算一个概率分布——在你当前输入的所有 token 条件下,哪个 token 出现的概率最高。
这个逻辑直接解释了为什么提示词要写得"符合模型的习惯"。你写"帮我写个函数,计算列表平均值,注意处理空列表",模型大概率表现优秀,因为这种表述在训练语料里反复出现过。但如果你写"搞快点,速度速度,列表搞一下平均值,空的咋整",模型也能猜,但猜测的分布就会分散,输出质量跟着波动。
我见过一个真实发生的情况:有人让模型写一个正则表达式,提示词写得非常简短——"邮箱正则",结果模型给出了一个基础匹配版本,没有考虑中文邮箱、没有处理边界情况。后来把需求描述完整,加了示例输入输出,同一个模型立刻给出了能过单测的版本。差别不在模型变了,而在 token 序列的指向性变了。
2.2 上下文窗口:你的临时工作台到底能放多少东西
上下文窗口(Context Window)是另一个绕不开的物理限制。它代表模型一次性能"看到"的 token 上限。你可以把它理解成一张临时工作台——台上能摆多少工具和材料是固定的,超出部分直接摆不下。
这张工作台的尺寸因模型而异,从几万 token 到几十万 token 都有。但绝大多数小白用不到那种夸张的长度,反而容易栽在另一个问题上:他们把重要的指令放在了窗口末尾,或者把关键约束埋在了一大堆废话中间。
这里有个非常重要的实操原则:模型对输入不同位置的注意力权重并不均匀。按我的经验,开头部分往往决定了整体方向,结尾部分对近期生成的 token 影响更大,中间部分最容易被"稀释"。所以,核心指令要放前面,关键约束要放前后两处都强调一遍,示例和背景材料放中间。
从我实际测试的多个模型来看,一个大致的规律是这样的:
| 输入位置 | 对输出的影响力 | 适合放什么 |
|---|---|---|
| 开头 | 高,决定任务方向和角色定位 | 角色设定、核心任务、输出格式总要求 |
| 中间 | 中,容易被长文本稀释 | 背景信息、参考资料、完整示例 |
| 结尾 | 高,直接影响模型生成完整个答案时的倾向 | 再次强调约束、输出格式提醒、最后的关键要素 |
我自己的模板习惯是:开头一段说清楚"你是谁、要干什么";中间放必要的信息和示例;结尾再来一句"记住,输出必须是 JSON 格式,并在最后给出三个测试用例"。这套结构配合下来,模型跑偏的概率低很多。
2.3 温度参数:让模型更稳还是更野
很多小白只知道调 Temperature 参数,却不知道它真正控制的是什么。简单说,Temperature 控制的是 token 采样的随机性。温度越低,模型越倾向于选择概率最高的 token,输出更稳定,但也会更机械;温度越高,模型越可能选择概率较低的 token,输出更有创造性,但更容易胡编。
在提示工程的实践中,它不是一个需要频繁动的旋钮,但在某些场景非常关键。比如你要模型输出严格结构的代码或配置,温度调低到 0 或 0.2 是合理的;你要模型帮你头脑风暴、起名字、写文案,温度调高到 0.7 到 0.9 会更有惊喜感。我见过不少人用默认温度去跑代码生成,结果同一个提示词跑三次,三次有一个是坏的,还以为是自己的问题。其实大概率就是温度太高的锅。
但温度不是救命的万能药。如果你的提示词本身就模糊,调低了也只是让它更坚定地生成一个稳定的错误答案。先调提示,再调温度,这是顺序问题,不能反。
3. 提示词的最小可运行结构:五个要素缺一不可
3.1 角色、任务、上下文、格式、示例
我在调提示词的时候,始终默认一个固定结构。这可能听起来很土,但你把它当成"最小可运行结构"来理解,就会发现它能覆盖 90% 的场景。五要素分别是:角色、任务、上下文、格式、示例。
- 角色:你希望模型用什么身份来回答。身份会影响模型的默认输出风格。给你一个开发同事的身份,它的表达会偏工程化;给它一个资深架构师的身份,它的回答会偏设计与权衡;给它一个老师的身份,它会更倾向解释原理。角色不是万能的,但它能把输出空间往一个方向推。
- 任务:一句话说清楚要干什么。这里要避免模糊动词,比如"处理一下"就远不如"将输入数据中的所有空字符串替换为默认值'未知'"。
- 上下文:模型需要的背景信息。比如代码库的框架、依赖的版本、数据的格式、已知的约束。上下文不是越多越好,而是越相关越好。
- 格式:输出应该长什么样。要不要列表?要不要表格?要不要代码块?是 JSON 还是纯文本?格式说明放在提示词里,能显著减少模型"自由发挥"的空间。
- 示例:给出输入输出的对照。对复杂任务来说,示例的力量远大于抽象描述。它像坐标一样把模型钉在你期望的轨道上。
这个结构不是谁发明的,而是我长期测试中沉淀出来的经验。你可以把它当成一个模板,每次写提示词都按这个顺序填空。
3.2 为什么示例比规则更有约束力
我用了很久才意识到一个奇怪的现象:规则会被模型"灵活理解",但示例会被模型"严格模仿"。如果你只是说"输出格式要一致",模型可能会时不时改变结构;但如果你给出两个输入输出对,让模型照葫芦画瓢,它的结构一致性立刻上升一个台阶。
原因是训练过程中的模式匹配:模型见过大量"根据示例推断规则"的语料,它天然会倾向于把任务理解成"照着这个模式继续写"。所以,当你遇到一个怎么描述都描述不清楚的需求时,别继续在规则上死磕,直接构造 2 到 3 个高质量的示例塞进去,效果立竿见影。
有个常见的误区是示例数量越多越好。实际并非如此。示例太少,模型找不到规律;示例太多,会占用上下文窗口,还会把模型的注意力从"任务"拉向"模仿"。我常用的量级是 2 到 5 个,视任务复杂度浮动。你选的示例要覆盖边界情况和典型情况,最好不要只给同一类型的样本。
拿一个真实场景打比方:我希望模型把一段用户反馈分类为"功能建议""Bug 报告""咨询"三类。如果只给一个示例,模型常常猜不准;但给三个示例,分别覆盖三种类型,并且在其中一个示例里故意放进一条语气很差但实际是功能建议的内容,模型的分类准确率肉眼可见地提升。这就是示例的约束力——它比规则更会"堵住"模型的歧义空间。
3.3 负面约束:哪些是你绝对不想要的
约束分两种:正向约束是"你必须做什么",负面约束是"你不要做什么"。我在初学阶段几乎只写正向约束,结果经常出现一个诡异的情况:模型把代码写得很漂亮,但用了项目里不存在的依赖库。
后来我发现,把"禁止项"显式写进提示词,比"请你注意使用项目已有依赖"这种软性表述有用得多。举个例子:"不要引用任何第三方库,只用 Python 标准库实现",这比"请使用简单的方案"清晰一百倍。
但负面约束不能滥用。你写一个长长的"不要做 A,不要做 B,不要做 C",模型反而会在这个负面列表上分配注意力,甚至在某些情况下为了避开 A 而做出更离谱的 D。更好的做法是:默认在正向路径上给足示例,然后把最重要的两三个负面约束单独放在结尾强调。比例控制在 1:3 左右——一条负面搭配三条正面。
4. 进阶策略:思维链、小样本与自我问答
4.1 思维链(Chain-of-Thought, CoT):让模型"把草稿纸给你看"
如果你让模型直接回答一个复杂问题,它往往会跳步,然后在某一步出错。人类的解题习惯是打草稿,模型其实也需要"草稿空间"。思维链的核心思路就是:在提示词里要求模型先给出推理过程,再给出最终答案。
最简单的方式是加一句"请一步一步思考,并把每一步推理写下来",或者更结构化的做法:要求它先列出已知条件,再写推理步骤,最后给结论。当模型把中间推理过程显性化时,它的每一步都更容易对齐你的预期,你甚至能定位它到底在哪一步理解错了。
这个技术我用在代码调试和逻辑推理场景时感受极深。有一次我需要模型帮我分析一个线上报错,同时给了完整的日志片段。如果不加 CoT,模型直接甩一个猜测;加了 CoT 后,它先罗列了日志里的关键异常时间点、依赖关系、最近一次发版信息,然后才给出诊断结果。前者的答案我根本不敢用,后者的答案我至少能跟着它的逻辑走一遍,确认有没有漏洞。
这里有一个进阶小技巧:CoT 不一定要用户去提示。有些模型本身具备隐式推理能力,你只要在提示词里给出一个"逐步思考"的示范,它就会自动按这个模式执行。这就是后面要说的 Few-shot 和 CoT 的结合。
4.2 Few-shot 与 Zero-shot 的选择边界
Zero-shot 就是直接丢任务,不加示例;Few-shot 则是给几个示例再让模型干活。大部分日常需求 Zero-shot 就够了,但遇到格式严格、分类维度多、输出规范复杂的任务时,Few-shot 是更稳的选择。
我自己的选择标准很简单:这个任务的输出有没有"标准答案的形状"。如果答案可以多种多样,Zero-shot 没问题;如果答案必须符合固定结构,Few-shot 几乎必须上。
有一个真实案例:我需要模型抽取合同里的关键日期,输出要求是"起始日、截止日、违约金计算基准日"三个字段,格式是统一的"YYYY-MM-DD"。直接 Zero-shot 时,模型偶尔会输出"合同签署后 30 天"这种相对时间;加上两个示例后,它学会了把相对时间换算成具体日期。这种效果是靠规则描述很难达到的,因为"换算规则"本身就很反直觉——哪个月按 30 天算?哪个月按 31 天算?模型只是靠示例反推了你的期望。
但 Few-shot 不是免费的午餐。它占用你的上下文空间,而且示例质量直接决定输出质量。如果你的示例本身不够典型、甚至有一个选错了,模型可能学到的"规则"比你想要的更宽或者更窄。
4.3 自我问答模式:让模型把隐含条件挖出来
有一种提示策略在复杂任务里很实用:先让模型把完成任务所需的隐含条件列出来,再让它自行回答这些条件,最后在满足所有条件的基础上产出最终结果。这个策略相当于把一个大任务拆成"问题生成 -> 逐个子问题解决 -> 整合"三个阶段。
打个比方,如果你让模型直接"为这个模块写测试用例",它写出来的可能就是几条基础用例。但如果你在提示词里要求它先思考:"这个模块的核心边界条件有哪些?输入什么值会触发异常?"然后再让它针对这些边界条件生成用例,产出的质量明显更接近一个工程师的实际水平。
这个方法还有一个副作用:它能替你发现你没考虑到的需求盲区。模型提出的隐含条件里,往往藏着你自己没写进需求但确实存在的东西。你可以把它当作一个免费的"需求评审工具"。
5. 实战调试:一份提示词从烂到可用的完整链路
5.1 拿到一个坏结果,先别急着改词
我刚开始学提示工程时,最常犯的错误是:结果不对就重新写一大段提示词,结果从"不准确"变成"更混乱"。后来我总结了一条线:先观察,再定位,最后修改。
观察什么?看模型输出的错误类型。错误大概分三类:结构错了(没按你要求的格式输出)、内容错了(理解偏离了任务)、风格错了(身份感不对)。这三种错误的解法完全不同。结构错了,加示例;内容错了,加上下文和约束;风格错了,改角色的描述方式。如果你不问"错在哪类",直接重写提示词,大概率是在碰运气。
我自己的调试流程是这样的:
- 先跑一次原始提示,拿到基线输出。
- 分析错误类型,记录它具体错在哪一段。
- 针对该错误类型做单点修改,其余部分不动。
- 再跑一次,对比输出差异。
- 如果改善,保留这个修改;如果没用,撤销并换另一个方向。
这个流程看起来简单,但它能防止你陷入"越改越乱"的循环。提示词工程和调代码很像:不要同时改多个变量。一次只动一个地方,才能知道哪个修改生效了。
5.2 结构化输出的激进用法:用语气和分隔符做锚点
在调试时我发现一个现象:模型对分隔符和语气词的敏感度远超预期。比如你要求"输出格式为 JSON",它偶尔会多说一句"以下是您需要的 JSON:"。这时候不要直接说"不要输出多余的话",而是在提示词里给出一个严格的包装模板:
"输出严格为以下形式,除此之外不要输出任何其他内容:\n{ \n "result": ...\n}"
这种给具体模板的做法,比抽象地要求"不要输出解释"有效得多。
另一个好用的锚点是"语气"。当你希望模型输出的代码风格非常严谨时,你可以说"像一位有十年经验、极其注重代码可读性的开发者那样写"。语气不是玄学,它是一种风格约束。它会把模型的词汇选择往更专业的方向牵引,直接反映在变量命名、注释风格和代码结构上。
5.3 回归测试:你的提示词也要防止"改好了又改坏"
这可能是提示工程里最容易忽略的环节。一段提示词今天改完效果不错,明天加了新需求后,原来的场景反而变差了。原因是你新增的内容干扰了原有的指令重心。
所以我建议你在项目里给重要提示词配上"回归用例"。每次修改前,先跑一遍之前验证过的几个核心输入,确保旧功能没有被破坏。不需要做得很重,一个简单的脚本就能实现:每轮修改后自动跑固定的输入集,输出差异一眼就能看出来。
我自己维护每个项目的提示词时,都会留一个"已验证用例集"的文档,里面存了 10 到 20 组典型输入。任何一次改动上线前,先跑一遍这个用例集。代价不大,但能省掉很多"上线后突然不 work 了"的尴尬。
6. 防坑指南:小白最容易踩的五个提示陷阱
6.1 陷阱一:术语堆砌,问题描述比需求还复杂
有些开发者写提示词像写代码规范一样,堆了一堆专业术语。结果模型被专业词汇带着跑,反而忽视了最简单的需求。比如你提"请用 SQL 实现一个带窗口函数的分页查询,注意索引优化",模型确实会给出一个复杂的查询,但它完全没考虑你数据库的实际表结构。
我的经验是:术语可以给,但要放在一句简单任务的后面。先用人话说明目标,再用术语补充细节。顺序不对,模型会优先理解"术语密集区"。
6.2 陷阱二:一次对话里塞太多任务
一个提示词最好只让模型干一件事。如果你让它"先分析这段代码,再优化,然后写测试用例,最后总结问题",大概率每个环节都会做得比较肤浅。模型不是不能做多任务,但它在长链路中会丢掉早期任务的重心。
解决办法是拆对话。一段对话专注一个目标,或者用"先做 A,确认无误后再做 B"的分步策略,让每次输出都有清晰的验收点。
6.3 陷阱三:忽略模型的"礼貌性后缀"
你会发现模型经常输出"希望这些信息对你有帮助""如果您需要进一步优化,请随时告诉我"这类废话。这不是 bug,但会污染结构化输出。
应对方式是把它当作格式约束的一部分,而不是抱怨它"废话多"。直接在格式说明里写:"不要在输出中包含任何礼貌用语、总结性评论或后续建议。"这类负面约束放在结尾,效果最好。
6.4 陷阱四:用变量命名和代码注释时的风格漂移
小白经常抱怨"同一个模型,上午写的代码风格和下午完全不一样"。这其实不是模型心情变了,而是你的提示词里没有风格锚点。
解决方法是每次都在提示词里固定一个风格描述,例如:"变量命名遵循 Python PEP8,类型标注完整,所有函数都带 Docstring。"把风格写出来,输出的一致性会好很多。甚至可以把一段你满意的历史输出作为风格样本直接附在提示词后面。
6.5 陷阱五:对输出结果不做校验
提示工程做得再好,模型的输出也只是一个概率采样,必然存在随机性和不确定性。把模型输出直接用于生产环境,总有一天会出事。
我会在提示词里要求模型输出结构化数据,然后在代码里做完整的 schema 校验和边界检查。如果校验失败,就重新用错误信息作为上下文再请求一次。这相当于在模型外面又加了一层容错机制,不把命运全部交给提示词。
7. 跨模型时代:提示词如何保持较高的可迁移性
现在模型更新速度太快,今天好用的提示词,换了新模型可能就失效。这个问题的根源在于:不同模型的训练方法、指令偏好和 tokenizer 都不一样。同一个提示词在不同模型上表现不同是常态,不是你写的有问题。
我的做法是给提示词分层。把"通用核心"和"特定优化"分开。通用核心是:角色设定、结构化输出要求、任务描述、示例。这部分基本不受模型版本影响。特定优化是:某个模型对某些措辞更敏感、某些模型对多轮对话的上下文权重不同。这部分只在需要时添加。
如果你在做比较复杂的项目,建议把提示词模板作为代码配置来管理,而不是散落在聊天记录里。用配置文件把它们统一存起来,配合版本管理。每次模型升级后,跑一遍回归用例,记录哪些提示词表现变了,再针对性地微调。这样不至于一下崩盘。
我在迁移模型时发现过一个有意思的现象:有些在旧模型上需要复杂示例才能约束的格式问题,新模型直接靠自然语言规则描述就能搞定。所以迁移时别急着保留所有旧技巧,先做减法,把最简单的版本跑一遍,再逐步添加约束。很多时候最简单的那一版反而已经够用了。
8. 落地方案:把提示词工程融入真实开发流程
8.1 从随手一问到模板沉淀
如果你只是把提示工程用在个人问答上,那前面的内容基本就够了。但如果你想让团队里的每个人都能稳定地产出高质量结果,就得把提示词当成代码资产来管理。
第一阶段是建模板库。把日常最常用的几类任务的提示词制作成模板,例如代码审查模板、Bug 分析模板、技术方案生成模板、日志解读模板。模板里用占位符表示可变部分,比如"待审查代码:{{code}}""报错日志:{{log}}"。
第二阶段是建设示例集。每个模板要配上 3 到 5 组已经验证过的示例输入和输出,这些示例既方便人理解怎么填模板,也方便在回归测试时使用。
第三阶段是反馈循环。每次使用后记录实际效果,定期更新模板。提示词模板和代码一样,需要维护版本迭代。一套不更新的模板库,半年后就会慢慢失效。
8.2 写提示词之前的准备:先把需求写成文档
这可能是最反直觉的建议:让你的提示词质量大幅度提升的方法,不是学更多技巧,而是先把需求写成一段清晰的、面向人类的文档。
我在做一个模块时,会先把"输入是什么、输出是什么、约束条件有哪些、边界情况有哪些"写成文字,哪怕只有几句话。这个动作表面上是给模型看的,其实是强迫自己把问题想清楚。需求混沌的人,写再长的提示词也构造不出稳定的输出。
我见过一个面试题,让候选人现场写提示词来生成一个特定功能的 Python 函数。表现最好的人往往不是提示词技巧最强的,而是那个先在纸上列清楚了函数输入输出和边界条件的人。这个动作的价值,无论在哪个模型时代都不会过时。
8.3 一个可直接套用的入门模板
给你一个可以直接用在大多数代码类任务上的模板结构:
"你是一个精通{{语言}}的资深开发者。请根据以下需求实现一个{{功能描述}}。要求:
- 代码风格遵循{{风格标准}};2. 需要处理以下边界情况:{{边界列表}};3. 不要使用{{禁止使用的库或工具}}。 示例输入与输出:{{示例}}。 请先给出完整代码,再对关键部分做简要解释。"
这个模板不是万能的,但它足够稳健,能满足大部分日常开发场景。你可以按自己的需求不断往里面加元素,比如要求代码附带单元测试、要求输出复杂度分析、要求先列出实现思路。
8.4 用提示词工程辅助学习编程
对小白程序员来说,提示工程还有一个很重要的用法:用它来学编程。你不需要一上来就死磕文档,可以把这个过程变成"问 -> 验证 -> 追问"的循环。
我第一次接触一个新框架时,会先要模型给我一个最小可运行示例,然后自己改参数、改逻辑,跑不通就把报错内容原样丢回去问。这个循环效率极高,而且每次追问都在训练你"如何更好地描述问题",这本身就是一种软技能。
但这里要提醒一句:模型给出的答案不总是正确的。代码类的输出尤其要验证。它的最大价值不是替你写代码,而是替你指路。你把它的答案当成参考资料,最终判断还是要靠实际运行结果。
9. 当你觉得提示词不够用时,问题可能不在提示词
聊到这儿,我想说点可能有点反主流的话。提示工程确实能解决很多问题,但并不是所有问题都应该用提示工程来解决。当你的需求已经稳定、输出规范非常明确、需要高性能低延迟的时候,你更应该考虑的是写一段确定性代码,而不是依赖模型的概率输出。
大模型适合的场景是:需求语义模糊、规则难以表达、输入边界不确定、需要灵活理解的任务。如果你的任务规则完全确定,比如"把字符串中的数字全部提取出来",直接写代码永远比提示词更可靠、更便宜、更快。
很多初学者容易掉进一个陷阱:凡是需求,先想怎么用模型解决。这就是本末倒置。正确的思路是先尝试确定性方案,解决不了或成本过高时,再引入提示工程。模型是工具,不是默认答案。
我实测过很多次,某些任务用正则表达式 10 行代码就能解决,根本不值得去调模型。提示工程的价值要发挥在它真正有优势的地方。分清"什么时候该用模型"和"怎么用模型",本身就是一种工程能力。
10. 最后的实操心法:从写完提示词到交付的四个步骤
最后分享一下我现在每次用完一段提示词之后的固定流程,算是一个可供你参考的收尾动作。
第一步,检查输出格式。代码能不能直接跑?JSON 能不能直接解析?先解决"长什么样"的问题。
第二步,检查边界覆盖。把输入里最容易出问题的几类数据丢进去,看结果是否正常。空值、极端值、异常格式、大量数据,这些边界情况至少跑一遍。
第三步,检查隐含依赖。输出的代码用了什么第三方库?版本对不对?引用的外部接口存在吗?模型经常在普通人看不到的细节上"合理胡编",这一步最花时间。
第四步,沉淀模板。跑通一次之后,把它整理成可复用的模板,配上刚才的示例输入和输出,归入模板库。下次遇到同类任务就不用从头开始写了。
这几步看起来简单,真正做到的人不多。大部分人的工作流是"写完提示词 -> 拿到结果 -> 粘贴走人",然后在下一次同样的需求上重新踩一遍坑。提示工程的价值,很多时候不在那个"灵光一现"的瞬间,而在你愿不愿意把跑通的东西固化下来。
我个人的体会是,提示工程最迷人的地方在于:你并不需要精通机器学习理论,只需要用一种清晰的、有条理的方式去和模型打交道,产出质量就会呈指数级提升。它像是把一个黑盒工具变成了你可控的队友,你需要做的就是学会怎么下指令、怎么验收、怎么迭代。这条路没有终点,但每一步都可以留下可复用的积累。