提示工程的核心技巧:把「玄学」变成可复用的工程方法

结构化提示、少样本示例、思维链、XML 标签分隔、输出格式约束——把提示词当成接口来设计,而不是当成咒语来试。

刚开始用大模型时,很多人把提示词当成咒语:效果好就抄下来,效果不好就换个说法再试。这种方式的问题在于不可复用、不可调试。

把提示词当成一个接口来设计,情况会好很多。接口有明确的输入、明确的输出格式、明确的失败模式。

一、先给模型一个角色和边界

最省力的改进往往是最简单的那个:告诉模型它是谁、要干什么、不要干什么。

你是一个代码审查助手。你的任务是找出这段 Python 代码中的 bug 和潜在风险。

只报告确实存在的问题,不要提出风格建议。
如果代码没有问题,直接回答"未发现问题"。

反面例子是一句「帮我看看这段代码」。模型不知道你要的是重构建议、性能分析还是找 bug,于是只能给出一个各方面都平庸的回答。

边界比角色更重要。 「不要提出风格建议」这样的排除性指令,往往比正面描述更能提升输出质量。

二、用 XML 标签把内容切开

当提示词里混合了指令、数据、示例时,模型很容易搞混。用标签分隔是最稳妥的做法:

请总结下面这篇文章的核心论点。

<article>
{{ARTICLE_TEXT}}
</article>

要求:
- 不超过 3 句话
- 使用中文
- 不要添加原文没有的信息

这样做有两个好处:

  1. 模型能清楚区分哪些是内容、哪些是指令,尤其是在内容里本身含有类似指令的句子时(这也能降低提示注入的风险)
  2. 你自己好维护,替换 {{ARTICLE_TEXT}} 就行,不用重新组织整个提示词

三、少样本示例:给一个,胜过解释十句

如果输出格式比较复杂,直接给例子比用文字描述格式有效得多:

从用户反馈中提取问题类型和情感倾向。

<examples>
<example>
<input>登录之后一直转圈,进不去首页</input>
<output>{"type": "bug", "sentiment": "negative", "summary": "登录后无法进入首页"}</output>
</example>
<example>
<input>希望能支持导出成 Excel</input>
<output>{"type": "feature_request", "sentiment": "neutral", "summary": "希望支持导出 Excel"}</output>
</example>
</examples>

<input>这个加载速度也太慢了吧,等了三分钟</input>

经验上 3 到 5 个示例是个甜点。示例要覆盖边界情况,而不只是典型情况——比如上面第二个例子特意放了一个非 bug 的请求。

四、思维链:让它先想再答

对于需要推理的任务,直接要答案容易出错;让它先写出推理过程,准确率会明显提升。

请先逐步分析,再给出结论。

<problem>
一个项目有 3 个开发者,A 的提交速度是 B 的 2 倍,C 比 B 慢 50%。
三人合作完成需要 6 天。问 B 单独完成需要几天?
</problem>

先写出你的推导过程,然后用 <answer> 标签给出最终答案。

关键点:把最终答案用标签包起来,方便程序解析。否则你拿到的是一大段推理文字,还得再写正则去抠答案。

另外要注意,思维链对推理类任务有效,但对「提取一个事实」「分类一条文本」这种任务收效甚微,反而浪费 token。别不加区分地到处加「让我们一步步思考」。

五、约束输出格式

如果你要把输出喂给下游程序,就别满足于「让它输出 JSON」——直接给出 schema:

以 JSON 格式输出,且只输出 JSON,不要有任何其他文字。

schema:
{
  "title": string,        // 不超过 20 字
  "tags": string[],       // 3 到 5 个
  "difficulty": "beginner" | "intermediate" | "advanced",
  "summary": string       // 50 到 100 字
}

注意 "difficulty" 用的是枚举而不是「难度等级」这样的自然语言描述。约束越具体,解析失败率越低。

即使这样,下游代码里仍然要做解析失败的兜底。模型偶尔会加个 ```json 代码块标记,或者多写一句“好的,以下是结果”。工程代码不能假设模型永远听话。

六、一个完整的模板

把上面几点合起来,一个可复用的提示词大概长这样:

# 角色
你是技术文档的编辑,擅长把晦涩的说明改写得清晰易读。

# 任务
改写 <doc> 中的文档片段,使其适合初学者阅读。

# 约束
- 保持技术准确性,不添加原文没有的信息
- 专业术语首次出现时给出中文解释
- 段落不超过 4 行
- 使用简体中文

# 输出格式
先输出改写后的内容,然后用 <changes> 标签列出你做了哪些修改及原因。

# 输入
<doc>
{{DOC_CONTENT}}
</doc>

七、迭代方法:把提示词当代码管理

最后,也是最重要的:别靠感觉调试提示词。

  • 建一个测试集:攒 15 到 30 条真实输入,覆盖典型情况和边界情况
  • 改动要单变量:一次只改一处,否则不知道是哪个改动起的作用
  • 记录失败案例:每次出问题就把输入存下来,它比成功案例更有价值
  • 版本化:提示词放进 Git,和代码一起 review

如果你在写代码,把提示词抽成常量或配置文件,别散落在业务逻辑里:

SYSTEM_PROMPT = """你是技术文档编辑..."""

def rewrite_doc(content: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=2048,
        system=SYSTEM_PROMPT,
        messages=[{
            "role": "user",
            "content": f"<doc>\n{content}\n</doc>"
        }],
    )
    return response.content[0].text

这样改提示词不用翻业务代码,也能单独做 A/B 对比。

小结

技巧 什么时候用
角色 + 边界 几乎总是有用,成本最低
XML 标签分隔 提示词里混有多种内容时
少样本示例 输出格式复杂或需要统一风格时
思维链 需要多步推理的任务
格式约束 + schema 输出要被程序解析时
测试集 + 版本管理 提示词要长期维护时

提示工程不是玄学,只是把「人怎么把需求说清楚」这件事工程化了。下一篇文章可以看看 RAG 检索增强,那是把外部知识喂给模型的正规做法。