Skip to content

提示工程实践

本页速览 从提示模板设计模式到结构化输出、从系统性调提示方法论到版本管理与回归测试,再到"提示 vs 微调 vs RAG"的决策树:把提示工程从"玄学"变成可复现、可度量的工程能力。

提示工程实践

提示工程不是"写一句漂亮的 prompt",而是围绕模型上下文窗口的一次受控实验设计:你设计输入分布,观察输出分布,并通过回归测试锁定改进。

提示(prompt)是 LLM 应用最廉价的"可编程接口"。本文回答三个问题:有哪些被验证过的模板模式?怎么系统性地调提示而不是靠手感?什么时候该停止调提示、转去微调或接 RAG? 全文假设你已理解提示工程的基础概念(角色、少样本、思维链)与推理基础的解码参数。

提示工程是"易用难精"

随便写几句就能让模型干活,但稳定地让模型按你要求的方式输出,是工程。本页的核心纪律:没有回归测试的提示修改,等于没有提交信息的代码修改——都无法回滚、无法归因。

一、提示工程在整个系统中的位置

一个典型 LLM 应用的失败,约 80% 出在"提示之外"(数据、上下文、评估、路由),但 80% 的人第一反应是"再改改提示"。先把决策顺序定下来:

问题出现


① 提示设计/修复(成本最低,先做)──── 本文主题

② 上下文供给(检索、工具、多轮记忆)── RAG / Agent

③ 模型升级(更强的基座)──────────── 模型选型

④ 微调(风格/格式/领域适配)──────── 微调实战

每一级都比上一级贵一个数量级。提示能解决的,绝不动模型——这条原则后面还会在决策树里展开。

二、提示模板设计模式

以下是经过公开实践验证的七种模式。任何模式都可能有效或无效,关键是用评测而不是感觉来判断(方法见第四节)。

模式一句话描述适用场景示例骨架
角色扮演给模型一个身份与立场风格统一、专业语气你是一位资深的 X 专家…
指令 + 约束明确任务 + 边界/格式绝大多数任务请做 X,不要做 Y,输出…
少样本示例(few-shot)给 2~5 个输入-输出示例需要模仿格式/口径输入:…\n输出:…
思维链(CoT)要求先推理再作答数学/逻辑/多步任务请一步步思考
思维树(ToT)显式探索多条路径并自评复杂规划、搜索型任务对分支进行打分与回溯
自我反思让模型检查并修正自己的输出问答、摘要、代码审查检查以上回答,指出错误并改正
结构化输出模板用模板/JSON 约束输出形状程序化消费输出以 JSON 输出:{"结论":…}

1. 指令 + 约束:最基础也最容易被忽略

text
任务:把下面的用户反馈归类为「投诉」「建议」「咨询」「其他」,并输出一句理由。
约束:
- 只输出 JSON,不要任何多余文字
- 理由不超过 20 个字
- 拿不准时归类为「其他」
用户反馈:你们的 App 又闪退了,气死我了!

约束的本质是缩小输出分布。观察发现:把"约束"从一句扩到四句(格式/长度/边界/兜底),稳定输出的比例显著提升——但约束要克制,过长的提示会稀释注意力(见第六节失败模式)。

2. 少样本示例:最强的"能力外挂"

少样本(in-context learning)能让模型在不更新参数的情况下模仿新格式、新口径。三个要点:

  • 示例要覆盖边界情况:好示例 = 2 个常规 + 1 个边界 + 1 个反例。
  • 示例与真实输入的格式严格一致:包括标点、换行、前缀。
  • 顺序敏感:模型对最近示例的模仿倾向最强,难例放最后。
text
请把句子改写为更礼貌的版本。

句子:把报告给我。
改写:麻烦您把报告发我一份,谢谢。

句子:这个 bug 太低级了。
改写:这个 bug 有点意外,麻烦确认一下根因。

句子:你快点。
改写:

3. 思维链(CoT):让模型"展示工作过程"

Let's think step by step 式提示对多步推理任务(GSM8K 数学题等)的提升已被广泛验证(参考资料)。工程化的 CoT 写法:

text
请回答下面的问题。在给出最终答案前,先写出你的推理步骤:
1. 列出已知条件
2. 逐步推导
3. 给出最终答案(用 ① 标记)

两个工程细节:

  • 把"推理"与"答案"分隔:让模型输出 推理:…\n答案:…,你就能在应用层只取答案、同时保留可审计的推理。
  • self-consistency:同一个问题采样 N 次(温度调高),对答案做多数投票,可显著提升准确率(以 3~5 倍推理成本换约 10~20% 相对提升,数值因任务而异)。这是"推理时扩展"最简单的一种,见规模法则中的 test-time scaling 讨论。

思维链的边界

CoT 对"需要多步逻辑"的任务有效,对"模型本就不会"的任务(幻觉高发)无效——它不能凭空创造知识。知识问题请走检索(RAG 实战)。

4. 系统提示(system prompt)的设计准则

系统提示是"对话的宪法":它在每次请求都注入,且模型对 system 部分的遵循度通常高于 user 部分(多数模型对 system 的权重更高)。四条设计准则:

准则做法反例
定义角色与边界一句话定位 + 明确"不做什么"只写"你是助手"
关键指令前置最重要的约束放开头埋在第 8 条之后
少写规则、多给示例格式用示例表达比用规则表达更稳二十条"请务必……"
固定版本、纳入版本管理system 是代码,走评审与回归在聊天框里随手改

system prompt 是攻击面

系统提示里的内容(尤其权限、工具描述、内部信息)会被提示注入尝试套取。不要把密钥、内部系统细节写进 system prompt;对不可信输入保持隔离,见安全与风险

三、结构化输出:让模型进入你的程序

LLM 的输出必须能被程序解析,这是生产落地的硬门槛。三条路线,从弱到强:

路线做法可靠性实现成本
提示约束 + 手动解析提示里规定 JSON 格式,代码里 json.loads + 兜底重试低~中(约 90% 以内)
JSON modeAPI 强制输出合法 JSON(不保证 schema)中(约 99% 合法 JSON)
结构化输出 / 工具调用API 按给定 JSON Schema 生成,或声明 function 让模型填充参数高(约 99% 符合 schema)

1. 用 few-shot 引导 JSON 输出(不依赖平台特性)

text
把下面的邮件转为 JSON。字段:type(inquiry/complaint/other)、urgency(low/medium/high)、summary。

邮件:您好,请问我昨天提交的退款申请什么时候能到账?比较着急。
输出:{"type": "inquiry", "urgency": "high", "summary": "询问退款到账时间"}

邮件:你们的会员价格太贵了,隔壁家只要一半。
输出:

2. JSON mode 与工具调用(以 OpenAI 风格 API 为例)

python
from openai import OpenAI

client = OpenAI()   # 读取环境变量 OPENAI_API_KEY

# 方案 A:response_format 强制合法 JSON
resp = client.chat.completions.create(
    model="gpt-4o-mini",
    response_format={"type": "json_object"},
    messages=[
        {"role": "system", "content": "提取信息,输出 JSON。"},
        {"role": "user", "content": "邮件:您的订单 O-1024 已发货,预计 3 天送达。"},
    ],
)
print(resp.choices[0].message.content)
# {"order_id": "O-1024", "status": "shipped", "eta_days": 3}

# 方案 B:函数调用(function calling),让模型"填参数"而非"写格式"
tools = [{
    "type": "function",
    "function": {
        "name": "extract_order_info",
        "description": "从邮件中提取订单信息",
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string"},
                "status": {"type": "string", "enum": ["shipped", "pending", "cancelled"]},
            },
            "required": ["order_id", "status"],
        },
    },
}]
resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "邮件:订单 O-1024 已发货。"}],
    tools=tools,
    tool_choice="auto",
)
print(resp.choices[0].message.tool_calls)   # 结构化参数,程序直接消费

别自己手写 JSON 解析硬抗

手写解析 + 重试在"演示项目"阶段可以,但生产中格式错误会引发连锁异常。JSON mode / function calling 是平台保证的接口,优先使用。开源自部署模型可借助 outlinesguidancexgrammar 等约束解码库达到同等效果(见框架与工具选型)。

四、系统性调提示:把玄学变成实验

1. 五步方法论

调提示的最高效路径,不是"想到哪改到哪",而是:

① 定基线(baseline)── 先跑通一个最简版本,记录其指标
② 提假设(hypothesis)── 明确"我改 X 是为了提升 Y"
③ 单变量控制(isolate)── 一次只改一个变量
④ 跑评测(measure)── 在固定测试集上量化对比
⑤ 回归(regress)── 确认新提示不破坏已有能力

每一步对应一个可复现的产物。特别是第 ④ 步:没有评测的提示优化是自我催眠。评测体系的搭建见评估实战

2. 一个可复现的对照实验

python
import json, random

# 固定的测试集(golden set):结构 = [{"input": ..., "expected": ...}, ...]
with open("test_cases.json") as f:
    test_cases = json.load(f)

def run_prompt(prompt_template: str, cases, sample_n=20) -> dict:
    """给定提示模板,在测试集子集上打分,返回正确率"""
    random.seed(42)
    sample = random.sample(cases, min(sample_n, len(cases)))
    correct = 0
    for case in sample:
        prompt = prompt_template.replace("{{INPUT}}", case["input"])
        output = call_model(prompt)          # 你自己的调用封装
        if normalize(output) == normalize(case["expected"]):
            correct += 1
    return {"accuracy": correct / len(sample), "n": len(sample)}

baseline = run_prompt(TEMPLATE_V1)
candidate = run_prompt(TEMPLATE_V2)
print(f"v1: {baseline}  v2: {candidate}")
# 只有候选显著高于基线才切换;样本量小时注意随机波动

变量控制的最小纪律

一次只改一个变量。把"加了示例 + 改了措辞 + 换了格式"混在一次改动里,你就再也无法归因是哪个改动起的作用。改完在同一份测试集上重测——换测试集的对比毫无意义。

3. 温度等解码参数也是"提示的一部分"

同样一段提示,temperature=0.7temperature=0 的行为完全不同。工程上先固定解码参数,再调提示文本;把解码参数写进实验记录,否则无法复现。

五、提示版本管理

提示是代码的一部分,必须被版本化管理:

手段做法解决什么问题
代码化存储提示写进仓库(.py/.json/.md),不散落在聊天窗口可 diff、可回滚
版本号每个提示有 v1v2 或语义版本号归因、沟通
回归测试关键路径提示纳入 CI:跑 golden set 才允许合并防止"改 A 坏 B"
变更记录记录每次改动的假设与结果避免重复踩坑
text
# prompts/extract_order_info/v3.md
### 变更记录
- v1: 初版,few-shot + 手写解析。accuracy 0.82
- v2: 改用 JSON mode。accuracy 0.91(修了非法 JSON 报错)
- v3: 增加 enum 约束字段 status。accuracy 0.95(修了状态值不规范)

提示与代码的边界

一次生产事故排查的常见结论是"提示被人在聊天里悄悄改过"。把提示当代码管(评审、测试、回滚),是 LLM 应用工程化水平的分水岭。

六、典型失败与修复对照表

失败现象根因修复示例
输出格式不稳定,偶尔多段废话约束不足 / 示例不够加格式约束、few-shot、JSON mode见第三节
拒绝回答太频繁系统提示过度防御 / 过度强调安全区分"危险"与"敏感"边界,收敛安全措辞相关讨论见安全与风险
提示越长效果越差上下文稀释:关键指令被淹没关键指令放开头和结尾;删冗余见下方示例
回答编造事实模型知识截止 / 长尾知识弱检索增强(RAG),见RAG 实战
少样本时强时弱示例质量/顺序不佳覆盖边界情况、难例放最后见第二节
被用户输入带偏(提示注入)未隔离不可信输入输入边界隔离、输出过滤安全与风险
换模型后行为突变提示过拟合于某模型跨模型回归测试、用更稳的约束手段见第四节

上下文稀释:提示越写越长的代价

一个典型反模式示例——把历史遗留的二十条规则全部堆进 system prompt,结果模型反而忽略关键指令:

text
# ❌ 反模式:超长系统提示,关键指令淹没在中段
你是客服机器人。请保持礼貌。我们的退款政策是……(500字)
请把用户反馈分类为 A/B/C。请用 JSON 输出……
text
# ✅ 修复:关键指令前置 + 精简
把用户反馈分类为 A/B/C,输出 JSON,禁止多余文字。
(政策说明放独立段落,必要时移到提示末尾,避免与关键指令竞争注意力。)

**"关键指令放开头和结尾"**是对提示工程中"注意力聚焦"原理的工程化运用。

提示注入的工程防御

提示注入(prompt injection)是"用户或第三方文本试图改写你的指令"。工程上的分级防御:

层级做法
输入隔离系统提示/指令与用户内容、检索内容分开放置且明确标记
内容过滤对检索内容、网页内容做脱敏与指令标记剥离
输出校验对模型输出做 schema/内容校验,重要动作需二次确认
权限最小化工具权限按最小授权设计,关键操作人工确认

原则:把"不可信输入"当作代码注入对待——永不直接拼接、永不隐式执行。完整威胁模型见安全与风险

七、"提示 vs 微调 vs RAG"决策树

遇到"模型不满足需求"时,按下面的顺序判断,不要跳跃:

模型输出不符合预期?
├─ 是知识/事实问题(不知道、编造)──→ RAG(补充外部知识)
├─ 是推理/格式问题(算错、不按格式)─→ 先提示(CoT、few-shot、结构化输出)
│   └─ 提示已到极限(不稳定、成本高)─→ 考虑微调(格式化/风格/领域)
├─ 是风格/口径/领域语言问题 ────────→ 微调 或 更详细的 few-shot
└─ 是能力天花板问题(模型本身不会)──→ 换更强的模型(或接受现状)
维度提示RAG微调
成本最低(只花推理 token)中(检索链路 + 推理)最高(训练 + 推理)
时效秒级更新分钟级更新(索引重建)需重新训练
知识注入弱(上下文有限)强(任意文档)强但固化
稳定性依赖模型,易漂移检索质量主导稳定
适用通用任务、快速迭代私有知识、时效知识输出格式、领域风格、任务口径

判断标准总结:能靠输入解决的(知识、示例、格式)先用提示和 RAG;只有"模型的内部行为"需要改变时(风格、专有任务)才微调。 微调的具体代价与流程见微调实战

延伸阅读

参考资料