[Prompt Engineering] 需求对齐与单提示词优化

Prompt Engineering 的核心是对齐——让大模型对需求的理解与预期一致。本文覆盖单提示词优化的四个维度:需求明确化、思维链推理引导、Few-shot 示例约束、结构化输出设计。

概述

在大模型的使用中,无论通过 ChatGPT 对话框、API 调用还是 Claude Code 这类编码助手,Prompt 是唯一的输入界面。需求、意图、约束、期望,全部压缩在数百到数千个 token 的文本中,没有侧通道,没有隐式约定。

由此引出一个核心问题:同样的模型、同样的任务,不同 prompt 的输出质量差异可以达数量级。 原因在于对齐——使用者脑中的需求模型与 LLM 基于预训练分布的语言模型之间存在天然偏差。以为说清楚了,模型以为理解了,产出的结果却可能完全不同。Prompt 优化,本质上是降低这个信息传输损耗。

单 Prompt 场景(一次输入、一次输出)的优化可以归纳为四个维度:

  1. 需求明确化:把需求说清楚而非写清楚
  2. 思维链(CoT):引导模型展示推理过程
  3. Few-shot:用示例精确约束输出
  4. 结构化输出:让输出可解析、可消费

一、需求明确化

四个要素

一个完整的 prompt 需要覆盖四个要素:

要素 含义 示例
背景 (Context) 在什么场景下提需求 “维护一个 Python 后端项目,技术栈 FastAPI + PostgreSQL”
目标 (Goal) 要产出什么 “审查 PR 代码,找出潜在性能问题”
约束 (Constraints) 边界条件——不做什么、格式要求 “只关注性能,不提代码风格;输出用 bullet list”
角色 (Role) 以什么身份视角回答 “资深 Python 后端工程师”

四个要素中,约束最容易被忽略。只写目标和背景、不明确”不要什么”,模型会输出包含大量无用信息的结果。例如代码审查时,没有约束的 prompt 会同时返回逻辑问题、风格建议、注释建议、命名建议,其中大量内容并非实际所需。问题不在模型能力,而在 prompt 没有划定边界。

模糊 prompt 与结构化 prompt 的对比:

模糊

1
帮我优化这个函数

结构化

1
2
3
4
5
6
7
[背景] 一个数据分析后端,该函数在高并发场景下为瓶颈。
[目标] 分析 perform_aggregation 的性能瓶颈并提出优化方案。
[约束]
- 只关注性能,不提代码风格
- 每个优化建议附预计收益(百分比)和实现复杂度
- 输出 Markdown 表格
[角色] 专精 Python 性能优化的后端工程师。

模糊 prompt 得到的可能是”可以考虑用缓存”这类泛泛建议;结构化 prompt 得到的是可操作的具体方案。输出质量的差异不在模型,在 prompt 提供的信息密度

角色设定的原理

角色设定并非玄学,其效果有合理解释:

  1. 激活特定分布:预训练语料中,不同角色的文本分布在不同语义空间。指定角色相当于划定采样范围
  2. 约束术语选择:指定”Python 后端工程师”,模型倾向于使用工程领域术语而非学术术语
  3. 隐含约束:角色自带行为模式——“代码审查者”自然输出审查意见,无需额外指令
1
[角色] Python 后端代码审查者,专精性能优化和安全性分析。

与笼统的”代码审查者”相比,越具体的角色,领域输出准确度越高。但也存在边界——角色过窄(如限定特定公司、特定年份的经验背景)反而导致模型行为不稳定。

实际使用中,建议用真实存在的职业身份——前端工程师、数据分析师、技术文档撰写者,或组合角色与领域专长,如”精通分布式系统的 SRE 工程师”。避免虚构角色(”精通所有编程语言的神”),这类角色模型无法有效解析。

二、思维链(Chain of Thought)

为什么 CoT 有效

LLM 是 next-token predictor,逐 token 生成内容。不加引导时,模型直接从问题跳到答案。对简单任务(翻译、事实查询)没问题,但对需要推理的任务(逻辑判断、代码调试、多步决策),跳跃式生成容易出错。

CoT(Chain of Thought)的核心思想:在 prompt 中要求模型展示推理步骤,而非直接给答案

以二分查找代码审查为例。不加 CoT:

1
2
3
4
5
6
7
8
9
10
11
12
以下代码是否有 bug?
def binary_search(arr, target):
left, right = 0, len(arr)
while left < right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid
else:
right = mid
return -1

模型可能直接回答”没有 bug”或”有 bug”但无法解释,结果不可靠。

加上 CoT:

1
2
3
4
以下代码是否有 bug?请逐步分析:
1. 理解函数意图
2. 逐行检查边界条件
3. 用测试用例验证结论

加入逐步推理指令后,模型输出完整分析过程,自身就能定位:left = mid 在特定条件下导致无限循环、right = len(arr) 在边界 case 下导致索引越界。

由此形成一个判断标准:如果需要看模型的推导过程来信任其结论,就需要 CoT

结构化推理框架

Let's think step by step 是通用做法,但结构化的推理步骤效果更稳定:

1
2
3
4
5
6
按以下步骤分析:
Step 1: 理解函数的输入输出约定
Step 2: 列出所有边界条件(空输入、单元素、重复值等)
Step 3: 逐一验证每个边界条件下的行为
Step 4: 指出所有潜在 bug,附上触发条件
Step 5: 给出修复建议

区别在于:通用 CoT 每一步的产出范围模糊,模型可能跳转到任意方向;结构化 CoT 每一步的产出范围被精确定义,模型不易偏离。原理类似于函数设计——职责越单一、边界越清晰,出错概率越低。

适用与不适用场景

需要 CoT 不需要 CoT
数学推理、逻辑判断 简单翻译
代码审查、调试 格式转换(CSV → JSON)
多步决策、方案设计 事实性查询(”Python 3.12 发布日期”)
需要排除法的场景 内容润色、改写、模板填充
“为什么”类问题 简单分类

一个直观的判断规则:如果这个问题需要人类”想一想”才能回答,模型也需要 CoT

Few-shot CoT

CoT 与 Few-shot 可以组合——在示例中嵌入推理步骤:

1
2
3
4
5
6
7
8
9
10
11
12
分析以下代码的 bug。

示例:
代码:def get_first(arr): return arr[0]
分析:
Step 1: 意图——返回数组第一个元素
Step 2: 边界条件——空列表 → IndexError
Step 3: Bug——没有处理空数组
建议:添加空检查,返回 None 或 raise

现在用同样步骤分析:
def divide(a, b): return a / b

示例教会模型分析的范式,CoT 规定推理流程。两者叠加后,模型对同类新输入基本不再需要额外引导。

三、Few-shot:用示例精确约束

示例 vs 描述

语言描述天然模糊。”输出要简洁”在不同语境下含义完全不同——一句话?一段话?50 字以内?示例则是精确的隐式规范——给模型一组输入-输出对,它通过模式匹配自动学习映射关系。

以客服邮件回复为例。描述式

1
请用简洁专业的语气回复客户邮件

结果:每次输出风格不一,时而生硬、时而啰嗦。

示例式

1
2
3
4
5
6
7
示例 1:
客户:你们的产品太烂了,退款!
回复:您好,非常抱歉给您带来不好的体验。退款将在 3-5 个工作日退回。如有其他问题,随时联系我们。

示例 2:
客户:发货怎么这么慢?
回复:您好,理解您的着急。经查询,您的订单预计明天发出,会优先处理。为不便深表歉意。

两个示例后,模型对所有类似邮件的回复风格高度一致——它通过示例学到”简洁专业”的具体内涵,而非通过对”简洁专业”四个字的语义猜测。

示例选择策略

三个原则:

  1. 多样性:覆盖不同类型的输入。只给”投诉”类示例,模型处理”咨询”类时就不稳定
  2. 边缘 Case:刻意放置棘手输入——信息不完整、带情绪、多个问题混杂。教会模型处理非标情况
  3. 格式一致性:所有示例的输出格式必须统一。示例 1 用”您好”开头、示例 2 用”Dear”开头,模型会在格式选择上混乱

数量与位置

2~4 个示例是稳定区间。1 个不够稳定,超过 5 个收益递减且消耗 context。

位置方面,示例放在指令之后、当前输入之前最有效:

1
2
3
4
5
6
7
8
9
10
11
12
[系统指令/Schema 说明]

[示例 1]
输入:...
输出:...

[示例 2]
输入:...
输出:...

[当前输入]
输入:...

Few-shot + 结构化输出

要求模型输出 JSON 时,示例是保证格式正确的最强手段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
分析代码问题,输出 JSON。

示例:
代码:
def get_user(id):
user = db.query(f"SELECT * FROM users WHERE id = {id}")
return user

输出:
{
"issues": [
{
"type": "security",
"severity": "critical",
"line": 2,
"description": "SQL 注入:直接拼接用户输入到查询中",
"fix": "使用参数化查询:db.query('SELECT * FROM users WHERE id = ?', [id])"
}
],
"summary": "发现 1 个严重安全问题"
}

现在分析以下代码,输出相同格式的 JSON:
[代码]

示例展示了填好的 Schema——比空 Schema 更精确,模型直接对照格式输出。

四、结构化输出

什么场景需要

单次对话场景下,输出格式可能不重要——输出是给人看的。但以下场景中,结构化输出是刚需:

  1. API 流水线:代码需要解析模型输出并传递给下一个环节
  2. Agent 系统:多个 Agent 间需要交换结构化信息
  3. 批量处理:需要从输出中提取特定字段进行统计
  4. UI 渲染:前端根据 JSON 字段展示不同组件

非结构化输出的代价是解析代码——LLM 的输出天然有波动,同样的 prompt 两次输出格式可能略有不同。解析代码要么脆弱(正则匹配一次失败就崩),要么复杂(需要容忍各种格式变体)。

两种实现路径

路径一:Prompt 内嵌 Schema

直接在 prompt 中描述期望的 JSON 格式,加”不要输出其他文字”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
输出以下 JSON 格式(不要包含其他文字):

{
"summary": "一句话总结",
"issues": [
{
"type": "bug | security | performance",
"file": "文件路径",
"line": 行号,
"description": "问题描述",
"suggestion": "修复建议"
}
]
}

优点:灵活,不需要 API 特定支持,改 prompt 即可迭代。缺点:模型可能在 JSON 前后加 markdown 代码块标记。

路径二:API 的 response_format 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
response = client.chat.completions.create(
model="gpt-4.1",
messages=[...],
response_format={
"type": "json_schema",
"json_schema": {
"name": "code_review",
"schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {"type": "string", "enum": ["bug", "security", "performance"]},
"file": {"type": "string"},
"line": {"type": "integer"},
"description": {"type": "string"},
"suggestion": {"type": "string"}
},
"required": ["type", "file", "line", "description", "suggestion"]
}
}
},
"required": ["summary", "issues"]
}
}
}
)

100% 保证输出符合 Schema,模型被强制约束。生产环境推荐此方式,解析代码零出错。OpenAI、DeepSeek 等主流厂商均已支持。

结构化输出的层次

不是所有场景都需要严格的 JSON Schema:

层次 格式 适用场景
L1 自由文本 对话、头脑风暴
L2 固定段落结构 邮件模板、报告生成
L3 Markdown 表格 对比分析、清单
L4 JSON (内联 Schema) API 调用链、轻量自动化
L5 JSON Schema (response_format) 生产级流水线、Agent 系统

选择能解决问题的最低层次——过度结构化增加 prompt 长度和推理成本。

五、四个维度的关系

四个维度不是独立使用的,在实际应用中通常按叠加顺序组合:

1
2
3
4
5
6
7
8
9
10
需求明确化(基础)


结构化输出(定义输出格式)


Few-shot(提供示例,填好 Schema)


CoT(需要推理时,示例中嵌入推理步骤)

一个包含全部四个维度的 prompt 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[背景] 维护 Python FastAPI 项目,需要代码审查
[角色] 资深 Python 后端工程师,专精性能和安全
[约束] 只关注逻辑问题和性能瓶颈,不提命名/风格

分析步骤:
Step 1: 理解函数的输入输出约定
Step 2: 逐行分析潜在逻辑错误
Step 3: 检查数据库/网络操作相关的性能风险
Step 4: 输出结构化审查结果

输出格式(JSON,不要包含其他文字):
{"issues": [{"type": "...", "line": 0, "description": "...", "suggestion": "..."}]}

示例:
输入:[有 SQL 注入的代码]
输出:{"issues": [{"type": "security", "line": 2, "description": "SQL 注入风险", "suggestion": "使用参数化查询"}]}

现在分析:
[当前代码]

总结

单 Prompt 优化归结为四个核心点:

  1. 需求明确化:背景、目标、约束、角色,四项缺一不可
  2. CoT:需要多步推理的场景,不要让模型跳步
  3. Few-shot:示例是比描述更强的信号,2~4 个高质量示例稳定可靠
  4. 结构化输出:当输出要进入流水线时,JSON Schema 省去所有解析开销

这四点覆盖了大部分单 Prompt 优化场景。当单 prompt 的物理极限——知识边界(模型不知道私有/最新信息)、context window 限制、成本控制——成为瓶颈时,需要引入 RAG 和多模型编排。