Prompt Engineering:从一句提示词到可维护系统
提示词不是孤立文本
我在早期项目里写过大量提示词——一开始就是几行文字,复制粘贴到一个常量文件里,上线前手动读两遍,觉得没问题就提交了。这种做法在 demo 阶段撑得住,进入生产后问题一个接一个冒出来:同样的提示词换个模型版本效果骤降,不同开发者的措辞风格互相打架,出了问题不知道是 prompt 的锅还是数据变了。
根本原因是我把提示词当成了「一段文字」,而不是「一个系统」。
2026 年行业里已经在讨论从 Prompt Engineering 到 Context Engineering 的演进——提示词工程并没有过时,但它已经是更大系统的子集[1]。你需要关心的不再只是「这句话怎么写」,而是模型在每次推理时看到的全部信息是怎么组织、检索、拼装和验证的。
从模板到系统的理论框架
提示词的层次模型
一个可维护的提示词系统可以拆成五层,每一层有独立的关注点和验证方式:
┌─────────────────────────────────────────────────────┐
│ L5 验证层:评测集、回归指标、人工审查流程 │
├─────────────────────────────────────────────────────┤
│ L4 输出契约层:字段定义、格式、长度、错误状态 │
├─────────────────────────────────────────────────────┤
│ L3 约束层:禁止行为、兜底策略、安全边界 │
├─────────────────────────────────────────────────────┤
│ L2 上下文层:输入数据、用户意图、检索结果 │
├─────────────────────────────────────────────────────┤
│ L1 目标层:任务定义、角色设定、核心指令 │
└─────────────────────────────────────────────────────┘这个分层不是理论摆设。arXiv 上有一篇关于 Promptware Engineering 的论文系统地论述了为什么提示词需要当作一等软件工件来管理——自然语言是无结构的、行为是概率性的、没有普适的正确性标准,这些特性让传统的软件工程方法必须做适配而不是直接套用[2]。
上下文工程的四个支柱
Sourcegraph 的工程博客把上下文工程拆成四个支柱,我觉得这个框架很实用[3]:
| 支柱 | 关注点 | 常见问题 |
|---|---|---|
| 系统指令 | 角色、约束、输出格式 | 指令矛盾、过度膨胀 |
| 检索 | 外部数据如何进入上下文窗口 | 检索质量差导致幻觉 |
| 记忆 | 短期对话历史 + 长期用户偏好 | 上下文窗口塞满后信息丢失 |
| 工具 | 可调用的函数定义和返回格式 | 工具集臃肿、选择歧义 |
这四个支柱之间不是独立关系——工具定义占 token,检索结果也占 token,记忆压缩又可能丢信息。上下文工程的核心矛盾是:模型的注意力预算有限,你要在有限窗口里最大化信号密度。
案例一:客服摘要生成的演进
v1:一句话提示词
请根据以下客服对话生成摘要:
{conversation}这种写法在测试三条对话时看起来没问题。上线两周后,客服主管发现摘要里混进了用户情绪判断(「用户非常愤怒」),但业务要求只记录事实。
v2:加了约束的提示词
你是一个客服摘要助手。请根据对话生成客观摘要。
规则:
- 只记录事实,不做情绪判断
- 使用中文
- 不超过 200 字
对话内容:
{conversation}好了一阵,又出问题了:有些对话涉及退款,摘要需要包含退款金额和原因,但提示词里没有定义输出结构,模型有时候写了有时候没写。
v3:结构化系统
# prompt/customer_summary.yaml
id: customer_summary
version: 3.1.0
model: claude-sonnet-4-20250514
temperature: 0.2
system: |
你是客服对话摘要引擎。任务是将对话转为结构化记录。
## 输出契约
严格按以下 JSON Schema 输出:
{
"summary": "string, 客观事实摘要, ≤200字",
"refund": {
"amount": "number | null, 退款金额(元)",
"reason": "string | null, 退款原因"
},
"sentiment": "neutral | negative | positive, 仅标注对话整体走向",
"action_items": ["string, 后续待办"]
}
## 约束
- 禁止使用「愤怒」「激动」等情绪形容词
- 退款信息仅在对话中明确提及时填写
- 若信息不足,对应字段填 null,不要推测
examples:
- input: "用户:我买的手机屏幕碎了..."
output:
summary: "用户反映手机屏幕碎裂,要求维修..."
refund: null
sentiment: negative
action_items: ["转接维修部门"]
eval_set: prompts/evals/customer_summary_v3.json从 v1 到 v3,变化的不是文字功底,而是工程化程度。v3 的提示词有版本号、有 schema、有评测集、有温度参数——它更像一个工程制品而不是一段文字。
案例二:代码审查助手的上下文拼装
代码审查场景能说明上下文工程四个支柱怎么协同工作。
这个流程里有几个关键决策:
| 决策点 | 选项 | 我最终的选择 | 原因 |
|---|---|---|---|
| 检索策略 | 全量加载 vs 按需检索 | 按需检索 | 大 PR 全量加载会超出窗口 |
| 记忆管理 | 每次全量历史 vs 压缩摘要 | 压缩摘要 | 保留关键决策,丢弃冗余输出 |
| 工具数量 | 12 个工具 vs 5 个工具 | 5 个工具 | 工具太多模型选择困难 |
| 输出格式 | 自由文本 vs 结构化 JSON | 结构化 JSON | 下游要自动分类审查意见 |
| 温度设置 | 0.0 vs 0.3 | 0.2 | 太低会重复,太高会不稳定 |
Anthropic 的工程博客特别提到了「just in time」的检索策略——不要预加载大量数据,而是让 agent 持有轻量引用,在运行时按需获取具体内容[4]。这个思路在代码审查场景里效果很明显:先给模型看 diff 概览和文件列表,让它自己决定要深入查看哪些文件。
案例三:多语言内容生成的版本管理
第三个案例来自内容生成场景。我们的平台需要支持中、英、日三种语言的内容生成,每种语言的提示词都有微妙的差异——不是简单翻译,而是语气、文化适配和合规要求都不同。
# ❌ 之前的做法:硬编码在业务逻辑里
def generate_content(topic: str, locale: str) -> str:
if locale == "zh-CN":
prompt = f"请写一篇关于{topic}的文章,要求专业但易懂"
elif locale == "en-US":
prompt = f"Write a professional article about {topic}"
elif locale == "ja-JP":
prompt = f"{topic}についてプロフェッショナルな記事を書いてください"
return llm.call(prompt)# ✅ 之后的做法:提示词作为可管理资源
# prompts/content_gen/zh-CN.yaml
id: content_generation
locale: zh-CN
version: 2.3.0
variables:
topic: { type: string, required: true }
tone: { type: enum, values: [professional, casual, technical], default: professional }
max_length: { type: integer, default: 2000 }
system: |
你是一位中文技术内容创作者。
## 风格要求
- 专业术语保留英文原文,首次出现时附中文注释
- 段落不超过 4 行,保持阅读节奏
- 禁止使用「赋能」「抓手」等空洞词汇
## 输出结构
1. 标题(≤20 字)
2. 导语(≤100 字,点明读者收益)
3. 正文(按逻辑分节,每节 300-500 字)
4. 关键要点(3-5 条)
template: |
主题:{{ topic }}
语气:{{ tone }}
字数上限:{{ max_length }}
eval_set: prompts/evals/content_gen_zh.json
quality_gates:
- check: length_within_bound
- check: no_banned_words
words: ["赋能", "抓手", "打通"]
- check: has_required_sections# prompts/content_gen/en-US.yaml — 独立版本,独立演进
id: content_generation
locale: en-US
version: 2.1.0
variables:
topic: { type: string, required: true }
tone: { type: enum, values: [professional, casual, technical], default: professional }
max_length: { type: integer, default: 2000 }
system: |
You are a technical content writer for an engineering blog.
## Style Requirements
- Use active voice
- One idea per paragraph, max 3 sentences
- Include code examples where relevant
## Output Structure
1. Title (≤60 chars)
2. Lead paragraph (≤80 words, state reader benefit)
3. Body (H2 sections, 200-400 words each)
4. Key takeaways (3-5 bullets)
template: |
Topic: {{ topic }}
Tone: {{ tone }}
Max words: {{ max_length }}
eval_set: prompts/evals/content_gen_en.json两个语言版本的提示词现在可以独立迭代。中文版本更新到 v2.3.0 时,英文版本停在 v2.1.0,互不影响。每个版本有自己的评测集和质量门禁。
提示词系统的工程化流程
从上面三个案例可以提炼出一套工程化流程。我把它画成流程图:
对应到具体实践:
需求分析阶段
├── 明确任务目标和成功指标
├── 梳理输入数据来源和可信度
└── 确定输出契约和错误处理策略
草稿编写阶段
├── 按五层模型组织提示词结构
├── 编写 3-5 个正例 + 2-3 个反例
└── 定义变量 schema 和默认值
本地评测阶段
├── 在评测集上跑回归测试
├── 检查输出格式是否符合契约
└── 对比上一版本的成功率变化
Code Review 阶段
├── 审查提示词逻辑和约束完整性
├── 确认不影响其他提示词的共享部分
└── 检查 token 用量和成本影响
金丝雀发布阶段
├── 10% 流量使用新版本
├── 监控关键指标(延迟、格式错误率、用户反馈)
└── 48 小时无异常后扩到 50%
全量发布 + 持续监控
├── 记录版本变更日志
├── 定期刷新评测集
└── 模型版本升级时重新跑回归关键决策对比
把「临时模板」和「可维护系统」两种做法放在一起对比:
| 维度 | 临时模板 | 可维护系统 |
|---|---|---|
| 存储位置 | 代码常量、配置文件散落在业务逻辑中 | 独立目录,YAML/JSON 格式,有 schema 校验 |
| 版本管理 | 跟代码一起 commit,没有独立版本号 | 语义化版本号,独立 changelog |
| 变量定义 | 字符串拼接或 f-string | 显式变量 schema,有类型和默认值 |
| 评测方式 | 手动试几条,感觉对了就上线 | 结构化评测集 + 自动化回归 + 质量门禁 |
| 发布流程 | 跟应用代码一起部署 | 独立发布,支持金丝雀和快速回滚 |
| 多语言支持 | if-else 硬编码 | 每个 locale 独立文件,独立演进 |
| 团队协作 | 谁都能改,没有审查流程 | PR review,变更需要说明动机和影响 |
| 成本意识 | 不关注 token 用量 | 监控 token 消耗,有预算告警 |
再看提示词内部结构的对比——把约束塞在一段话里 vs 结构化分层:
# ❌ 所有要求混在一起
你是一个专业的客服助手,要礼貌、专业,不能泄露用户隐私,
回答要简洁不超过200字,如果不确定就说不知道,
不要编造信息,使用中文回答,遇到投诉要安抚用户情绪,
同时记录投诉内容并转接主管。
# ✅ 分层结构化
# 角色
你是客服对话助手,负责处理用户咨询和投诉。
# 输出契约
- 语言:中文
- 长度:≤200 字
- 格式:纯文本,段落间空行分隔
# 行为约束
- 禁止泄露用户个人信息(手机号、地址、订单号)
- 不确定的信息明确说「我不确定」,不推测
- 遇到投诉:先共情 → 记录要点 → 提供转接选项
# 兜底策略
- 超出能力范围的问题 → 引导转人工
- 系统异常 → 回复「请稍后重试」并记录错误# ❌ 评测靠感觉
def test_prompt():
result = llm.call(prompt, input="屏幕碎了怎么办")
assert "维修" in result # 只看关键词
print("OK")
# ✅ 结构化评测
def test_customer_summary():
eval_cases = load_eval_set("prompts/evals/customer_summary_v3.json")
results = run_batch(prompt_version="3.1.0", cases=eval_cases)
assert results.format_compliance >= 0.98 # JSON 格式合规率
assert results.field_coverage >= 0.95 # 必填字段覆盖率
assert results.factual_accuracy >= 0.90 # 事实准确率(LLM-as-judge)
assert results.no_emotion_words == 1.0 # 不含情绪形容词
# 与上一版本对比
prev = load_baseline("3.0.0")
assert results.compare(prev).no_regression()# ❌ 版本管理缺失
# 不知道什么时候改的、为什么改、谁改的
# prompt = "生成摘要,要简短"
# ✅ 语义化版本 + 变更日志
# id: customer_summary
# version: 3.1.0
# changelog:
# 3.1.0 (2026-06-20): 增加 refund 字段,适配新退款流程
# 3.0.0 (2026-05-15): 重写输出契约,从自由文本改为 JSON
# 2.2.1 (2026-04-08): 修复情绪词泄露问题
# 2.2.0 (2026-03-20): 增加 action_items 字段生产上线前的检查清单
每次提示词要上生产之前,我会过一遍这份清单。它不是一次性写好的,而是每次出事后加一条,慢慢攒到现在的规模:
结构与可维护性
- 提示词有独立版本号(语义化版本)
- 变量有显式 schema 定义(类型、必填、默认值)
- 系统指令、约束、输出契约分层书写
- 共享部分(如通用安全约束)抽成可复用片段,避免重复
评测与质量
- 有正例评测集(≥10 条,覆盖主要场景)
- 有反例评测集(≥5 条,覆盖边界和异常情况)
- 评测集包含多语言/多区域场景(如适用)
- 自动化回归测试通过,与上一版本无显著退化
- 输出格式合规率 ≥ 98%
安全与合规
- 不包含用户敏感信息(PII)
- 有明确的禁止行为列表
- 有兜底策略(模型不确定时怎么处理)
- 经过 prompt injection 测试
发布与监控
- 变更日志记录了本次修改的动机和预期影响
- 金丝雀发布计划已准备(10% → 50% → 100%)
- 线上监控指标已配置(延迟、错误率、格式合规率)
- 回滚方案已验证
成本与性能
- Token 用量在预算范围内
- 检索结果有截断策略,不会把窗口塞满
- 工具定义数量精简(≤8 个),选择无歧义
我踩过的坑
写这篇文章不是因为我有一套完美方法论,而是因为踩了足够多的坑。几个印象深刻的:
坑一:提示词里写了业务逻辑。早期我在提示词里写了一堆 if-else 规则(「如果用户提到退款且金额大于 500 元则……」)。后来发现这些规则用代码判断比让模型判断可靠得多。现在我的原则是:能确定性验证的逻辑放代码,需要判断力的指令放提示词。
坑二:评测集和提示词一起更新。有一次发现评测集的预期输出是照着提示词改的——提示词改了输出格式,评测集也跟着改了,回归测试永远通过。后来把评测集的维护权限和提示词分开了,评测集变更需要独立审批。
坑三:忽视模型版本升级的影响。某个模型小版本升级后,提示词的 JSON 输出偶尔会多出几个字段。没有 breaking change 通知,但下游解析代码崩了。现在每次模型版本升级都跑完整回归,即使只是 patch 版本。
坑四:工具定义太贪心。代码审查场景一开始定义了 12 个工具,模型经常在工具选择上犯迷糊,调用顺序也不对。砍到 5 个之后,准确率从 72% 涨到 91%。工具不是越多越好,正确的选择必须对模型来说毫无歧义。
结语
从「写一句提示词」到「维护一个提示词系统」,核心转变是:提示词不再是写完就不动的静态文本,而是一个需要版本管理、评测验证、灰度发布和持续监控的工程制品。
这个转变和微服务架构的演进有相似之处——单体应用拆成微服务后,每个服务都需要独立的 CI/CD、监控和告警。提示词从业务代码里拆出来后,同样需要自己的工程基础设施。
不需要一步到位。先给提示词加版本号,再建评测集,再做灰度发布——每一步都能解决真实问题。
参考资料
- BigData Boutique. Context Engineering > Prompt Engineering: What Changed. 2026.
- Xu et al. Software Engineering for Prompt-Enabled Systems. arXiv, 2025.
- Sourcegraph. Context Engineering: A Practical Guide for AI Agents. 2026.
- Anthropic. Effective Context Engineering for AI Agents. 2025.
- DataX Power. System Prompting as Engineering Practice in 2026. 2025.
- Paul, K. From Experimentation to Production: Managing the Prompt Engineering Lifecycle. Dev.to, 2025.
- ACM. A Systematic Prompt Template Analysis for Real-world LLM Apps. 2025.
- Anthropic. Prompting Best Practices — Claude API Documentation. 2025.