附录 K:Prompt Engineering — 不是玄学,是工程

全书各处散落了 prompt 片段——system prompt 里加一句、工具定义里改一行。
但 Prompt Engineering 可以是一门系统工程。本附录提供一个可复现的工作流。
K.1 为什么”试出来的 Prompt”不可靠
大多数人的 Prompt Engineering 流程是这样的:1
2
3
4
5
6
71. 写一个 prompt
2. 试几个例子,感觉还行
3. 上线
4. 某天发现某个场景崩了
5. 改一下 prompt
6. 其他场景又崩了
7. 回到步骤 3
这个循环的问题在于:没有度量,就无法改进。
Prompt Engineering 的系统工程方法只有三个步骤:定义指标 → 对照实验 → 迭代收敛。
K.2 核心策略库
Chain of Thought(思维链)
让模型”一步一步想”:1
2
3
4
5
6❌ 普通 prompt:
这段代码有什么问题?
✅ CoT prompt:
这段代码有什么问题?先列出所有可疑的地方,然后逐一分析,
最后给出结论。每个分析步骤注明你参考了代码的哪一行。
对 Agent 的适用性:中等。CoT 在推理任务上有效,但 Agent 已经有 Thinking(ch59),两者重复。在 system prompt 中用轻量 CoT 即可。
Few-Shot(少样本)
给模型几个例子:1
2
3
4
5
6
7
8
9
10
11## 工具调用示例
当用户说"看看 auth.ts",你应该:
→ tool_use: Read, input: { file_path: "src/auth.ts" }
当用户说"修复这个文件",你应该:
→ 先 tool_use: Read, input: { file_path: "..." }
→ 再 tool_use: Edit, input: { file_path: "...", old_string: "...", new_string: "..." }
当用户说"跑一下测试",你应该:
→ tool_use: Bash, input: { command: "npm test", description: "Run test suite" }
对 Agent 的适用性:高。Few-shot 对工具选择准确率提升显著。在你的 Agent 框架中,为每个工具提供 2-3 个使用示例。
ReAct(推理+行动交替)
这是 Agent 的原生模式——不需要额外设计。Agentic Loop 本身就是 ReAct:1
2
3
4
5
6
7
8Thought: 我需要看 auth.ts 的内容
Action: Read("src/auth.ts")
Observation: "function login() { ... }"
Thought: login函数没有处理空密码
Action: Edit("src/auth.ts", old, new)
Observation: 文件已修改
Thought: 需要验证修改是否正确
Action: Bash("npm test")
结构化输出约束
当 Agent 需要返回特定格式时:1
2
3
4
5
6
7
8
9当你完成任务后,以以下格式返回结果:
```json
{
"status": "success" | "failure",
"summary": "一句话总结做了什么",
"files_changed": ["file1.ts", "file2.ts"],
"tests_passed": true | false
}1
2
3
4
5
6
7
配合 `output_config.format` 参数(ch61)使用。
---
## K.3 迭代优化工作流
┌───────────────────────────────────────────────────────┐
│ 迭代循环 │
│ │
│ 1. 写 prompt v1 │
│ ↓ │
│ 2. 在评估集上跑 → 得基准分数 │
│ ↓ │
│ 3. 改 prompt → v2 │
│ ↓ │
│ 4. 在相同评估集上跑 → 比较 v1 vs v2 │
│ ↓ │
│ 5. 如果进步 → v2 成为新基准 | 如果退步 → 回退到 v1 │
│ ↓ │
│ 6. 重复 3-5 直到收敛(连续 3 轮无显著改进) │
└───────────────────────────────────────────────────────┘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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
```typescript
// → prompt-optimizer.ts — Prompt 优化器
interface PromptVariant {
id: string
prompt: string
scores: EvalScores[] // 多次评估的分数
}
class PromptOptimizer {
private variants: PromptVariant[] = []
private evaluator: AgentEvaluator
// 添加一个变体
addVariant(id: string, prompt: string): void {
this.variants.push({ id, prompt, scores: [] })
}
// 评估所有变体
async evaluateAll(tasks: EvalTask[], runsPerVariant = 3): Promise<void> {
for (const variant of this.variants) {
for (let i = 0; i < runsPerVariant; i++) {
const scores = await this.evaluator.evaluate(variant.prompt, tasks)
variant.scores.push(scores)
}
}
}
// 找最佳变体
best(): PromptVariant {
return this.variants
.map(v => ({
variant: v,
avgScore: v.scores.reduce((a, b) => a + b.successRate, 0) / v.scores.length,
}))
.sort((a, b) => b.avgScore - a.avgScore)[0]
.variant
}
// 检查收敛
hasConverged(variant: PromptVariant, threshold = 0.02): boolean {
const last3 = variant.scores.slice(-3)
if (last3.length < 3) return false
const max = Math.max(...last3.map(s => s.successRate))
const min = Math.min(...last3.map(s => s.successRate))
return (max - min) < threshold // 最后 3 次评估差异 < 2%
}
}
常见迭代方向
| 问题 | 可能的原因 | 改进方向 |
|---|---|---|
| 工具选择错误 | prompt 中的工具描述不够清晰 | 为每个工具加 Few-shot 示例 |
| Agent 过早退出 | system prompt 中的结束条件太宽松 | 加”只有确认全部通过后才能结束” |
| Agent 不读文件就改 | prompt 中没有强调”先读后改” | 在 system prompt 开头加规则 |
| Agent 问太多问题 | prompt 中”不确定时问用户”太宽 | 修改为”只在不可逆操作前问用户” |
K.4 System Prompt 设计模板
1 | # 第一部分:身份与边界(~15%) |
各部分的比例不是固定的——取决于任务。安全审查类 Agent 的”不可逾越规则”部分可能占 30%。
各部分的优先级
模型对 System Prompt 不同位置的注意力不同:1
2
3开头 (前 20%) → 注意力最高,放行为规则
中间 (20-80%) → 放工具定义和示例
结尾 (后 20%) → 放输出格式要求
关键:不要把所有规则堆在开头。模型倾向于关注开头和结尾的内容。
K.5 多模型供应商的 Prompt 适配
同一个 prompt 在不同模型上的效果可能完全不同。附录E 讲了多模型概念,这里讲实践:
| 差异维度 | Claude | GPT-4 | Gemini |
|---|---|---|---|
| System prompt 理解 | 严格遵守 | 严格遵守 | 偶尔被忽略 |
| Few-shot 效果 | 中等 | 强 | 强 |
| 工具调用稳定性 | 最高 | 高 | 中 |
| 长 prompt 注意力 | 均匀 | 偏前后 | 偏前 |
| 中文 prompt 质量 | 好 | 好 | 最好 |
多模型适配策略:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// → 多模型 Prompt 适配
function adaptPrompt(basePrompt: string, model: string): string {
switch (model.split("-")[0]) { // 按模型家族适配
case "claude":
// Claude 对工具定义长度敏感 → 精简
return basePrompt.replace(/## 可用工具[\s\S]*?(?=##|$)/, compressToolDefs)
case "gpt":
// GPT 对 Few-shot 敏感 → 保留完整示例
return basePrompt // 不需要缩减
case "gemini":
// Gemini 对中文理解好 → 不需要额外翻译提示
return basePrompt
default:
return basePrompt
}
}
K.6 Prompt 的测试
prompt 也是代码。它应该被测试:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// → prompt.test.ts
import { describe, it, expect } from "vitest"
describe("System Prompt", () => {
it("contains all required sections", () => {
expect(prompt).toContain("# 第一部分:身份与边界")
expect(prompt).toContain("# 第五部分:不可逾越规则")
})
it("does not exceed max length", () => {
// Anthropic 建议 system prompt < 10K tokens
expect(estimateTokens(prompt)).toBeLessThan(10_000)
})
it("has balanced section ratios", () => {
const rules = extractSection(prompt, "行为规则")
const tools = extractSection(prompt, "可用工具")
// 行为规则不超过工具定义的 1.5 倍
expect(rules.length / tools.length).toBeLessThan(1.5)
})
})
试一试
- 评估你当前的 system prompt。用附录G 的评估 runner 在 10 个任务上跑,记录 baseline。
- 改一个变量。只改 system prompt 中的一个部分(如给每个工具加一个 Few-shot 示例),重新评估。
- 迭代 3 轮。每次只改一个变量,记录变化。哪次改进最显著?
- 设计一个 Prompt 测试。为你的 system prompt 写 3 个单元测试。
Prompt Engineering 不是”写得更巧妙”。是”度量 → 实验 → 收敛”。